Эх сурвалжийг харах

[core] Revert re-formating of wsgiserver (#1244)

Romain 5 жил өмнө
parent
commit
d9a0662d87

+ 1679 - 1679
desktop/core/src/desktop/lib/wsgiserver.py

@@ -22,38 +22,38 @@ A high-speed, production ready, thread pooled, generic WSGI server.
 Simplest example on how to use this module directly
 (without using CherryPy's application machinery):
 
-  from cherrypy import wsgiserver
+    from cherrypy import wsgiserver
 
-  def my_crazy_app(environ, start_response):
-    status = '200 OK'
-    response_headers = [('Content-type','text/plain')]
-    start_response(status, response_headers)
-    return ['Hello world!\n']
+    def my_crazy_app(environ, start_response):
+        status = '200 OK'
+        response_headers = [('Content-type','text/plain')]
+        start_response(status, response_headers)
+        return ['Hello world!\n']
 
-  server = wsgiserver.CherryPyWSGIServer(
-        ('0.0.0.0', 8070), my_crazy_app,
-        server_name='www.cherrypy.example')
+    server = wsgiserver.CherryPyWSGIServer(
+                ('0.0.0.0', 8070), my_crazy_app,
+                server_name='www.cherrypy.example')
 
 The CherryPy WSGI server can serve as many WSGI applications
 as you want in one instance by using a WSGIPathInfoDispatcher:
 
-  d = WSGIPathInfoDispatcher({'/': my_crazy_app, '/blog': my_blog_app})
-  server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 80), d)
+    d = WSGIPathInfoDispatcher({'/': my_crazy_app, '/blog': my_blog_app})
+    server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 80), d)
 
 Want SSL support? Just set these attributes:
 
-  server.ssl_certificate = <filename>
-  server.ssl_private_key = <filename>
+    server.ssl_certificate = <filename>
+    server.ssl_private_key = <filename>
 
 Supports also SSL certificate chains with this attribute:
 
-  server.ssl_certificate_chain = <filename>
+    server.ssl_certificate_chain = <filename>
 
-  if __name__ == '__main__':
-    try:
-      server.start()
-    except KeyboardInterrupt:
-      server.stop()
+    if __name__ == '__main__':
+        try:
+            server.start()
+        except KeyboardInterrupt:
+            server.stop()
 
 This won't call the CherryPy engine (application side) at all, only the
 WSGI server, which is independant from the rest of CherryPy. Don't
@@ -64,39 +64,39 @@ For those of you wanting to understand internals of this module, here's the
 basic call flow. The server's listening thread runs a very tight loop,
 sticking incoming connections onto a Queue:
 
-  server = CherryPyWSGIServer(...)
-  server.start()
-  while True:
-    tick()
-    # This blocks until a request comes in:
-    child = socket.accept()
-    conn = HTTPConnection(child, ...)
-    server.requests.put(conn)
+    server = CherryPyWSGIServer(...)
+    server.start()
+    while True:
+        tick()
+        # This blocks until a request comes in:
+        child = socket.accept()
+        conn = HTTPConnection(child, ...)
+        server.requests.put(conn)
 
 Worker threads are kept in a pool and poll the Queue, popping off and then
 handling each connection in turn. Each connection can consist of an arbitrary
 number of requests and their responses, so we run a nested loop:
 
-  while True:
-    conn = server.requests.get()
-    conn.communicate()
-    ->  while True:
-        req = HTTPRequest(...)
-        req.parse_request()
-        ->  # Read the Request-Line, e.g. "GET /page HTTP/1.1"
-          req.rfile.readline()
-          req.read_headers()
-        req.respond()
-        ->  response = wsgi_app(...)
-          try:
-            for chunk in response:
-              if chunk:
-                req.write(chunk)
-          finally:
-            if hasattr(response, "close"):
-              response.close()
-        if req.close_connection:
-          return
+    while True:
+        conn = server.requests.get()
+        conn.communicate()
+        ->  while True:
+                req = HTTPRequest(...)
+                req.parse_request()
+                ->  # Read the Request-Line, e.g. "GET /page HTTP/1.1"
+                    req.rfile.readline()
+                    req.read_headers()
+                req.respond()
+                ->  response = wsgi_app(...)
+                    try:
+                        for chunk in response:
+                            if chunk:
+                                req.write(chunk)
+                    finally:
+                        if hasattr(response, "close"):
+                            response.close()
+                if req.close_connection:
+                    return
 """
 
 
@@ -114,9 +114,9 @@ quoted_slash = re.compile("(?i)%2F")
 from email.utils import formatdate
 import socket
 try:
-  import io as StringIO
+    import io as StringIO
 except ImportError:
-  import io
+    import io
 
 _fileobject_uses_str_type = isinstance(socket._fileobject(None)._rbuf, basestring)
 
@@ -127,10 +127,10 @@ import traceback
 import warnings
 
 try:
-  from OpenSSL import SSL
-  from OpenSSL import crypto
+    from OpenSSL import SSL
+    from OpenSSL import crypto
 except ImportError:
-  SSL = None
+    SSL = None
 
 import errno
 import logging
@@ -149,1741 +149,1741 @@ LOG = logging.getLogger(__name__)
 
 
 def plat_specific_errors(*errnames):
-  """Return error numbers for all errors in errnames on this platform.
+    """Return error numbers for all errors in errnames on this platform.
 
-  The 'errno' module contains different global constants depending on
-  the specific platform (OS). This function will return the list of
-  numeric values for a given list of potential names.
-  """
-  errno_names = dir(errno)
-  nums = [getattr(errno, k) for k in errnames if k in errno_names]
-  # de-dupe the list
-  return list(dict.fromkeys(nums).keys())
+    The 'errno' module contains different global constants depending on
+    the specific platform (OS). This function will return the list of
+    numeric values for a given list of potential names.
+    """
+    errno_names = dir(errno)
+    nums = [getattr(errno, k) for k in errnames if k in errno_names]
+    # de-dupe the list
+    return list(dict.fromkeys(nums).keys())
 
 socket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR")
 
 socket_errors_to_ignore = plat_specific_errors(
-  "EPIPE",
-  "EBADF", "WSAEBADF",
-  "ENOTSOCK", "WSAENOTSOCK",
-  "ETIMEDOUT", "WSAETIMEDOUT",
-  "ECONNREFUSED", "WSAECONNREFUSED",
-  "ECONNRESET", "WSAECONNRESET",
-  "ECONNABORTED", "WSAECONNABORTED",
-  "ENETRESET", "WSAENETRESET",
-  "EHOSTDOWN", "EHOSTUNREACH",
-  )
+    "EPIPE",
+    "EBADF", "WSAEBADF",
+    "ENOTSOCK", "WSAENOTSOCK",
+    "ETIMEDOUT", "WSAETIMEDOUT",
+    "ECONNREFUSED", "WSAECONNREFUSED",
+    "ECONNRESET", "WSAECONNRESET",
+    "ECONNABORTED", "WSAECONNABORTED",
+    "ENETRESET", "WSAENETRESET",
+    "EHOSTDOWN", "EHOSTUNREACH",
+    )
 socket_errors_to_ignore.append("timed out")
 
 socket_errors_nonblocking = plat_specific_errors(
-  'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK')
+    'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK')
 
 comma_separated_headers = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING',
-  'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL',
-  'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT',
-  'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE',
-  'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING',
-  'WWW-AUTHENTICATE']
+    'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL',
+    'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT',
+    'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE',
+    'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING',
+    'WWW-AUTHENTICATE']
 
 
 class WSGIPathInfoDispatcher(object):
-  """A WSGI dispatcher for dispatch based on the PATH_INFO.
+    """A WSGI dispatcher for dispatch based on the PATH_INFO.
 
-  apps: a dict or list of (path_prefix, app) pairs.
-  """
+    apps: a dict or list of (path_prefix, app) pairs.
+    """
 
-  def __init__(self, apps):
-    try:
-      apps = list(apps.items())
-    except AttributeError:
-      pass
+    def __init__(self, apps):
+        try:
+            apps = list(apps.items())
+        except AttributeError:
+            pass
 
-    # Sort the apps by len(path), descending
-    apps.sort()
-    apps.reverse()
+        # Sort the apps by len(path), descending
+        apps.sort()
+        apps.reverse()
 
-    # The path_prefix strings must start, but not end, with a slash.
-    # Use "" instead of "/".
-    self.apps = [(p.rstrip("/"), a) for p, a in apps]
+        # The path_prefix strings must start, but not end, with a slash.
+        # Use "" instead of "/".
+        self.apps = [(p.rstrip("/"), a) for p, a in apps]
 
-  def __call__(self, environ, start_response):
-    path = environ["PATH_INFO"] or "/"
-    for p, app in self.apps:
-      # The apps list should be sorted by length, descending.
-      if path.startswith(p + "/") or path == p:
-        environ = environ.copy()
-        environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p
-        environ["PATH_INFO"] = path[len(p):]
-        return app(environ, start_response)
+    def __call__(self, environ, start_response):
+        path = environ["PATH_INFO"] or "/"
+        for p, app in self.apps:
+            # The apps list should be sorted by length, descending.
+            if path.startswith(p + "/") or path == p:
+                environ = environ.copy()
+                environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p
+                environ["PATH_INFO"] = path[len(p):]
+                return app(environ, start_response)
 
-    start_response('404 Not Found', [('Content-Type', 'text/plain'),
-                     ('Content-Length', '0')])
-    return ['']
+        start_response('404 Not Found', [('Content-Type', 'text/plain'),
+                                         ('Content-Length', '0')])
+        return ['']
 
 
 class MaxSizeExceeded(Exception):
-  pass
+    pass
 
 class SizeCheckWrapper(object):
-  """Wraps a file-like object, raising MaxSizeExceeded if too large."""
-
-  def __init__(self, rfile, maxlen):
-    self.rfile = rfile
-    self.maxlen = maxlen
-    self.bytes_read = 0
-
-  def _check_length(self):
-    if self.maxlen and self.bytes_read > self.maxlen:
-      raise MaxSizeExceeded()
-
-  def read(self, size=None):
-    data = self.rfile.read(size)
-    self.bytes_read += len(data)
-    self._check_length()
-    return data
-
-  def readline(self, size=None):
-    if size is not None:
-      data = self.rfile.readline(size)
-      self.bytes_read += len(data)
-      self._check_length()
-      return data
-
-    # User didn't specify a size ...
-    # We read the line in chunks to make sure it's not a 100MB line !
-    res = []
-    while True:
-      data = self.rfile.readline(256)
-      self.bytes_read += len(data)
-      self._check_length()
-      res.append(data)
-      # See http://www.cherrypy.org/ticket/421
-      if len(data) < 256 or data[-1:] == "\n":
-        return ''.join(res)
-
-  def readlines(self, sizehint=0):
-    # Shamelessly stolen from StringIO
-    total = 0
-    lines = []
-    line = self.readline()
-    while line:
-      lines.append(line)
-      total += len(line)
-      if 0 < sizehint <= total:
-        break
-      line = self.readline()
-    return lines
-
-  def close(self):
-    self.rfile.close()
-
-  def __iter__(self):
-    return self
-
-  def __next__(self):
-    data = next(self.rfile)
-    self.bytes_read += len(data)
-    self._check_length()
-    return data
+    """Wraps a file-like object, raising MaxSizeExceeded if too large."""
+
+    def __init__(self, rfile, maxlen):
+        self.rfile = rfile
+        self.maxlen = maxlen
+        self.bytes_read = 0
+
+    def _check_length(self):
+        if self.maxlen and self.bytes_read > self.maxlen:
+            raise MaxSizeExceeded()
+
+    def read(self, size=None):
+        data = self.rfile.read(size)
+        self.bytes_read += len(data)
+        self._check_length()
+        return data
+
+    def readline(self, size=None):
+        if size is not None:
+            data = self.rfile.readline(size)
+            self.bytes_read += len(data)
+            self._check_length()
+            return data
 
+        # User didn't specify a size ...
+        # We read the line in chunks to make sure it's not a 100MB line !
+        res = []
+        while True:
+            data = self.rfile.readline(256)
+            self.bytes_read += len(data)
+            self._check_length()
+            res.append(data)
+            # See http://www.cherrypy.org/ticket/421
+            if len(data) < 256 or data[-1:] == "\n":
+                return ''.join(res)
+
+    def readlines(self, sizehint=0):
+        # Shamelessly stolen from StringIO
+        total = 0
+        lines = []
+        line = self.readline()
+        while line:
+            lines.append(line)
+            total += len(line)
+            if 0 < sizehint <= total:
+                break
+            line = self.readline()
+        return lines
+
+    def close(self):
+        self.rfile.close()
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        data = next(self.rfile)
+        self.bytes_read += len(data)
+        self._check_length()
+        return data
 
-class HTTPRequest(object):
-  """An HTTP Request (and response).
-
-  A single HTTP connection may consist of multiple request/response pairs.
-
-  send: the 'send' method from the connection's socket object.
-  wsgi_app: the WSGI application to call.
-  environ: a partial WSGI environ (server and connection entries).
-    The caller MUST set the following entries:
-    * All wsgi.* entries, including .input
-    * SERVER_NAME and SERVER_PORT
-    * Any SSL_* entries
-    * Any custom entries like REMOTE_ADDR and REMOTE_PORT
-    * SERVER_SOFTWARE: the value to write in the "Server" response header.
-    * ACTUAL_SERVER_PROTOCOL: the value to write in the Status-Line of
-      the response. From RFC 2145: "An HTTP server SHOULD send a
-      response version equal to the highest version for which the
-      server is at least conditionally compliant, and whose major
-      version is less than or equal to the one received in the
-      request.  An HTTP server MUST NOT send a version for which
-      it is not at least conditionally compliant."
-
-  outheaders: a list of header tuples to write in the response.
-  ready: when True, the request has been parsed and is ready to begin
-    generating the response. When False, signals the calling Connection
-    that the response should not be generated and the connection should
-    close.
-  close_connection: signals the calling Connection that the request
-    should close. This does not imply an error! The client and/or
-    server may each request that the connection be closed.
-  chunked_write: if True, output will be encoded with the "chunked"
-    transfer-coding. This value is set automatically inside
-    send_headers.
-  """
-
-  max_request_header_size = 0
-  max_request_body_size = 0
-
-  def __init__(self, wfile, environ, wsgi_app):
-    self.rfile = environ['wsgi.input']
-    self.wfile = wfile
-    self.environ = environ.copy()
-    self.wsgi_app = wsgi_app
-
-    self.ready = False
-    self.started_request = False
-    self.started_response = False
-    self.status = ""
-    self.outheaders = []
-    self.sent_headers = False
-    self.close_connection = False
-    self.chunked_write = False
-
-  def parse_request(self):
-    """Parse the next HTTP request start-line and message-headers."""
-    self.rfile.maxlen = self.max_request_header_size
-    self.rfile.bytes_read = 0
 
-    try:
-      self._parse_request()
-    except MaxSizeExceeded:
-      self.simple_response("413 Request Entity Too Large")
-      return
-
-  def _parse_request(self):
-    # HTTP/1.1 connections are persistent by default. If a client
-    # requests a page, then idles (leaves the connection open),
-    # then rfile.readline() will raise socket.error("timed out").
-    # Note that it does this based on the value given to settimeout(),
-    # and doesn't need the client to request or acknowledge the close
-    # (although your TCP stack might suffer for it: cf Apache's history
-    # with FIN_WAIT_2).
-    request_line = self.rfile.readline()
-    # Set started_request to True so communicate() knows to send 408
-    # from here on out.
-    self.started_request = True
-    if not request_line:
-      # Force self.ready = False so the connection will close.
-      self.ready = False
-      return
-
-    if request_line == "\r\n":
-      # RFC 2616 sec 4.1: "...if the server is reading the protocol
-      # stream at the beginning of a message and receives a CRLF
-      # first, it should ignore the CRLF."
-      # But only ignore one leading line! else we enable a DoS.
-      request_line = self.rfile.readline()
-      if not request_line:
-        self.ready = False
-        return
+class HTTPRequest(object):
+    """An HTTP Request (and response).
+
+    A single HTTP connection may consist of multiple request/response pairs.
+
+    send: the 'send' method from the connection's socket object.
+    wsgi_app: the WSGI application to call.
+    environ: a partial WSGI environ (server and connection entries).
+        The caller MUST set the following entries:
+        * All wsgi.* entries, including .input
+        * SERVER_NAME and SERVER_PORT
+        * Any SSL_* entries
+        * Any custom entries like REMOTE_ADDR and REMOTE_PORT
+        * SERVER_SOFTWARE: the value to write in the "Server" response header.
+        * ACTUAL_SERVER_PROTOCOL: the value to write in the Status-Line of
+            the response. From RFC 2145: "An HTTP server SHOULD send a
+            response version equal to the highest version for which the
+            server is at least conditionally compliant, and whose major
+            version is less than or equal to the one received in the
+            request.  An HTTP server MUST NOT send a version for which
+            it is not at least conditionally compliant."
+
+    outheaders: a list of header tuples to write in the response.
+    ready: when True, the request has been parsed and is ready to begin
+        generating the response. When False, signals the calling Connection
+        that the response should not be generated and the connection should
+        close.
+    close_connection: signals the calling Connection that the request
+        should close. This does not imply an error! The client and/or
+        server may each request that the connection be closed.
+    chunked_write: if True, output will be encoded with the "chunked"
+        transfer-coding. This value is set automatically inside
+        send_headers.
+    """
 
-    environ = self.environ
+    max_request_header_size = 0
+    max_request_body_size = 0
 
-    try:
-      method, path, req_protocol = request_line.strip().split(" ", 2)
-    except ValueError:
-      self.simple_response(400, "Malformed Request-Line")
-      return
-
-    environ["REQUEST_METHOD"] = method
-
-    # path may be an abs_path (including "http://host.domain.tld");
-    scheme, location, path, params, qs, frag = lib_urlparse(path)
-
-    if frag:
-      self.simple_response("400 Bad Request",
-                 "Illegal #fragment in Request-URI.")
-      return
-
-    if scheme:
-      environ["wsgi.url_scheme"] = scheme
-    if params:
-      path = path + ";" + params
-
-    environ["SCRIPT_NAME"] = ""
-
-    # Unquote the path+params (e.g. "/this%20path" -> "this path").
-    # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
-    #
-    # But note that "...a URI must be separated into its components
-    # before the escaped characters within those components can be
-    # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
-    atoms = [urllib_unquote(x) for x in quoted_slash.split(path)]
-    path = "%2F".join(atoms)
-    environ["PATH_INFO"] = path
-
-    # Note that, like wsgiref and most other WSGI servers,
-    # we unquote the path but not the query string.
-    environ["QUERY_STRING"] = qs
-
-    # Compare request and server HTTP protocol versions, in case our
-    # server does not support the requested protocol. Limit our output
-    # to min(req, server). We want the following output:
-    #     request    server     actual written   supported response
-    #     protocol   protocol  response protocol    feature set
-    # a     1.0        1.0           1.0                1.0
-    # b     1.0        1.1           1.1                1.0
-    # c     1.1        1.0           1.0                1.0
-    # d     1.1        1.1           1.1                1.1
-    # Notice that, in (b), the response will be "HTTP/1.1" even though
-    # the client only understands 1.0. RFC 2616 10.5.6 says we should
-    # only return 505 if the _major_ version is different.
-    rp = int(req_protocol[5]), int(req_protocol[7])
-    server_protocol = environ["ACTUAL_SERVER_PROTOCOL"]
-    sp = int(server_protocol[5]), int(server_protocol[7])
-    if sp[0] != rp[0]:
-      self.simple_response("505 HTTP Version Not Supported")
-      return
-    # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol.
-    environ["SERVER_PROTOCOL"] = req_protocol
-    self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
-
-    # If the Request-URI was an absoluteURI, use its location atom.
-    if location:
-      environ["SERVER_NAME"] = location
-
-    # then all the http headers
-    try:
-      self.read_headers()
-    except ValueError as ex:
-      self.simple_response("400 Bad Request", repr(ex.args))
-      return
-
-    mrbs = self.max_request_body_size
-    if mrbs and int(environ.get("CONTENT_LENGTH", 0)) > mrbs:
-      self.simple_response("413 Request Entity Too Large")
-      return
-
-    # Persistent connection support
-    if self.response_protocol == "HTTP/1.1":
-      # Both server and client are HTTP/1.1
-      if environ.get("HTTP_CONNECTION", "") == "close":
-        self.close_connection = True
-    else:
-      # Either the server or client (or both) are HTTP/1.0
-      if environ.get("HTTP_CONNECTION", "") != "Keep-Alive":
-        self.close_connection = True
-
-    # Transfer-Encoding support
-    te = None
-    if self.response_protocol == "HTTP/1.1":
-      te = environ.get("HTTP_TRANSFER_ENCODING")
-      if te:
-        te = [x.strip().lower() for x in te.split(",") if x.strip()]
-
-    self.chunked_read = False
-
-    if te:
-      for enc in te:
-        if enc == "chunked":
-          self.chunked_read = True
-        else:
-          # Note that, even if we see "chunked", we must reject
-          # if there is an extension we don't recognize.
-          self.simple_response("501 Unimplemented")
-          self.close_connection = True
-          return
-
-    # From PEP 333:
-    # "Servers and gateways that implement HTTP 1.1 must provide
-    # transparent support for HTTP 1.1's "expect/continue" mechanism.
-    # This may be done in any of several ways:
-    #   1. Respond to requests containing an Expect: 100-continue request
-    #      with an immediate "100 Continue" response, and proceed normally.
-    #   2. Proceed with the request normally, but provide the application
-    #      with a wsgi.input stream that will send the "100 Continue"
-    #      response if/when the application first attempts to read from
-    #      the input stream. The read request must then remain blocked
-    #      until the client responds.
-    #   3. Wait until the client decides that the server does not support
-    #      expect/continue, and sends the request body on its own.
-    #      (This is suboptimal, and is not recommended.)
-    #
-    # We used to do 3, but are now doing 1. Maybe we'll do 2 someday,
-    # but it seems like it would be a big slowdown for such a rare case.
-    if environ.get("HTTP_EXPECT", "") == "100-continue":
-      self.simple_response(100)
-
-    self.ready = True
-
-  def read_headers(self):
-    """Read header lines from the incoming stream."""
-    environ = self.environ
+    def __init__(self, wfile, environ, wsgi_app):
+        self.rfile = environ['wsgi.input']
+        self.wfile = wfile
+        self.environ = environ.copy()
+        self.wsgi_app = wsgi_app
 
-    while True:
-      line = self.rfile.readline()
-      if not line:
-        # No more data--illegal end of headers
-        raise ValueError("Illegal end of headers.")
-
-      if line == '\r\n':
-        # Normal end of headers
-        break
-
-      if line[0] in ' \t':
-        # It's a continuation line.
-        v = line.strip()
-      else:
-        k, v = line.split(":", 1)
-        k, v = k.strip().upper(), v.strip()
-        envname = "HTTP_" + k.replace("-", "_")
-
-      if k in comma_separated_headers:
-        existing = environ.get(envname)
-        if existing:
-          v = ", ".join((existing, v))
-      environ[envname] = v
-
-    ct = environ.pop("HTTP_CONTENT_TYPE", None)
-    if ct is not None:
-      environ["CONTENT_TYPE"] = ct
-    cl = environ.pop("HTTP_CONTENT_LENGTH", None)
-    if cl is not None:
-      environ["CONTENT_LENGTH"] = cl
-
-  def decode_chunked(self):
-    """Decode the 'chunked' transfer coding."""
-    cl = 0
-    data = string_io()
-    while True:
-      line = self.rfile.readline().strip().split(";", 1)
-      chunk_size = int(line.pop(0), 16)
-      if chunk_size <= 0:
-        break
-##            if line: chunk_extension = line[0]
-      cl += chunk_size
-      data.write(self.rfile.read(chunk_size))
-      crlf = self.rfile.read(2)
-      if crlf != "\r\n":
-        self.simple_response("400 Bad Request",
-                   "Bad chunked transfer coding "
-                   "(expected '\\r\\n', got %r)" % crlf)
-        return
-
-    # Grab any trailer headers
-    self.read_headers()
-
-    data.seek(0)
-    self.environ["wsgi.input"] = data
-    self.environ["CONTENT_LENGTH"] = str(cl) or ""
-    return True
-
-  def respond(self):
-    """Call the appropriate WSGI app and write its iterable output."""
-    # Set rfile.maxlen to ensure we don't read past Content-Length.
-    # This will also be used to read the entire request body if errors
-    # are raised before the app can read the body.
-    if self.chunked_read:
-      # If chunked, Content-Length will be 0.
-      self.rfile.maxlen = self.max_request_body_size
-    else:
-      cl = int(self.environ.get("CONTENT_LENGTH", 0))
-      if self.max_request_body_size:
-        self.rfile.maxlen = min(cl, self.max_request_body_size)
-      else:
-        self.rfile.maxlen = cl
-    self.rfile.bytes_read = 0
+        self.ready = False
+        self.started_request = False
+        self.started_response = False
+        self.status = ""
+        self.outheaders = []
+        self.sent_headers = False
+        self.close_connection = False
+        self.chunked_write = False
+
+    def parse_request(self):
+        """Parse the next HTTP request start-line and message-headers."""
+        self.rfile.maxlen = self.max_request_header_size
+        self.rfile.bytes_read = 0
 
-    try:
-      self._respond()
-    except MaxSizeExceeded:
-      if not self.sent_headers:
-        self.simple_response("413 Request Entity Too Large")
-      return
-
-  def _respond(self):
-    if self.chunked_read:
-      if not self.decode_chunked():
-        self.close_connection = True
-        return
-
-    response = self.wsgi_app(self.environ, self.start_response)
-    try:
-      for chunk in response:
-        # "The start_response callable must not actually transmit
-        # the response headers. Instead, it must store them for the
-        # server or gateway to transmit only after the first
-        # iteration of the application return value that yields
-        # a NON-EMPTY string, or upon the application's first
-        # invocation of the write() callable." (PEP 333)
-        if chunk:
-          self.write(chunk)
-    finally:
-      if hasattr(response, "close"):
-        response.close()
-
-    if (self.ready and not self.sent_headers):
-      self.sent_headers = True
-      self.send_headers()
-    if self.chunked_write:
-      self.wfile.sendall("0\r\n\r\n")
-
-  def simple_response(self, status, msg=""):
-    """Write a simple response back to the client."""
-    status = str(status)
-    buf = ["%s %s\r\n" % (self.environ['ACTUAL_SERVER_PROTOCOL'], status),
-         "Content-Length: %s\r\n" % len(msg),
-         "Content-Type: text/plain\r\n"]
-
-    if status[:3] == "413" and self.response_protocol == 'HTTP/1.1':
-      # Request Entity Too Large
-      self.close_connection = True
-      buf.append("Connection: close\r\n")
-
-    buf.append("\r\n")
-    if msg:
-      buf.append(msg)
+        try:
+            self._parse_request()
+        except MaxSizeExceeded:
+            self.simple_response("413 Request Entity Too Large")
+            return
 
-    try:
-      self.wfile.sendall("".join(buf))
-    except socket.error as x:
-      if x.args[0] not in socket_errors_to_ignore:
-        raise
-
-  def start_response(self, status, headers, exc_info=None):
-    """WSGI callable to begin the HTTP response."""
-    # "The application may call start_response more than once,
-    # if and only if the exc_info argument is provided."
-    if self.started_response and not exc_info:
-      raise AssertionError("WSGI start_response called a second "
-                 "time with no exc_info.")
-
-    # "if exc_info is provided, and the HTTP headers have already been
-    # sent, start_response must raise an error, and should raise the
-    # exc_info tuple."
-    if self.sent_headers:
-      try:
-        raise (exc_info[0], exc_info[1], exc_info[2])
-      finally:
-        exc_info = None
-
-    self.started_response = True
-    self.status = status
-    self.outheaders.extend(headers)
-    return self.write
-
-  def write(self, chunk):
-    """WSGI callable to write unbuffered data to the client.
-
-    This method is also used internally by start_response (to write
-    data from the iterable returned by the WSGI application).
-    """
-    if not self.started_response:
-      raise AssertionError("WSGI write called before start_response.")
+    def _parse_request(self):
+        # HTTP/1.1 connections are persistent by default. If a client
+        # requests a page, then idles (leaves the connection open),
+        # then rfile.readline() will raise socket.error("timed out").
+        # Note that it does this based on the value given to settimeout(),
+        # and doesn't need the client to request or acknowledge the close
+        # (although your TCP stack might suffer for it: cf Apache's history
+        # with FIN_WAIT_2).
+        request_line = self.rfile.readline()
+        # Set started_request to True so communicate() knows to send 408
+        # from here on out.
+        self.started_request = True
+        if not request_line:
+            # Force self.ready = False so the connection will close.
+            self.ready = False
+            return
 
-    if not self.sent_headers:
-      self.sent_headers = True
-      self.send_headers()
+        if request_line == "\r\n":
+            # RFC 2616 sec 4.1: "...if the server is reading the protocol
+            # stream at the beginning of a message and receives a CRLF
+            # first, it should ignore the CRLF."
+            # But only ignore one leading line! else we enable a DoS.
+            request_line = self.rfile.readline()
+            if not request_line:
+                self.ready = False
+                return
 
-    if self.chunked_write and chunk:
-      buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"]
-      self.wfile.sendall("".join(buf))
-    else:
-      self.wfile.sendall(chunk)
-
-  def send_headers(self):
-    """Assert, process, and send the HTTP response message-headers."""
-    hkeys = [key.lower() for key, value in self.outheaders]
-    status = int(self.status[:3])
-
-    if status == 413:
-      # Request Entity Too Large. Close conn to avoid garbage.
-      self.close_connection = True
-    elif "content-length" not in hkeys:
-      # "All 1xx (informational), 204 (no content),
-      # and 304 (not modified) responses MUST NOT
-      # include a message-body." So no point chunking.
-      if status < 200 or status in (204, 205, 304):
-        pass
-      else:
-        if (self.response_protocol == 'HTTP/1.1'
-          and self.environ["REQUEST_METHOD"] != 'HEAD'):
-          # Use the chunked transfer-coding
-          self.chunked_write = True
-          self.outheaders.append(("Transfer-Encoding", "chunked"))
-        else:
-          # Closing the conn is the only way to determine len.
-          self.close_connection = True
-
-    if "connection" not in hkeys:
-      if self.response_protocol == 'HTTP/1.1':
-        # Both server and client are HTTP/1.1 or better
-        if self.close_connection:
-          self.outheaders.append(("Connection", "close"))
-      else:
-        # Server and/or client are HTTP/1.0
-        if not self.close_connection:
-          self.outheaders.append(("Connection", "Keep-Alive"))
-
-    if (not self.close_connection) and (not self.chunked_read):
-      # Read any remaining request body data on the socket.
-      # "If an origin server receives a request that does not include an
-      # Expect request-header field with the "100-continue" expectation,
-      # the request includes a request body, and the server responds
-      # with a final status code before reading the entire request body
-      # from the transport connection, then the server SHOULD NOT close
-      # the transport connection until it has read the entire request,
-      # or until the client closes the connection. Otherwise, the client
-      # might not reliably receive the response message. However, this
-      # requirement is not be construed as preventing a server from
-      # defending itself against denial-of-service attacks, or from
-      # badly broken client implementations."
-      size = self.rfile.maxlen - self.rfile.bytes_read
-      if size > 0:
-        self.rfile.read(size)
-
-    if "date" not in hkeys:
-      self.outheaders.append(("Date", formatdate()))
-
-    if "server" not in hkeys:
-      self.outheaders.append(("Server", self.environ['SERVER_SOFTWARE']))
-
-    buf = [self.environ['ACTUAL_SERVER_PROTOCOL'], " ", self.status, "\r\n"]
-    try:
-      buf += [k + ": " + v + "\r\n" for k, v in self.outheaders]
-    except TypeError:
-      if not isinstance(k, str):
-        raise TypeError("WSGI response header key %r is not a string.")
-      if not isinstance(v, str):
-        raise TypeError("WSGI response header value %r is not a string.")
-      else:
-        raise
-    buf.append("\r\n")
-    self.wfile.sendall("".join(buf))
+        environ = self.environ
 
+        try:
+            method, path, req_protocol = request_line.strip().split(" ", 2)
+        except ValueError:
+            self.simple_response(400, "Malformed Request-Line")
+            return
 
-class NoSSLError(Exception):
-  """Exception raised when a client speaks HTTP to an HTTPS socket."""
-  pass
+        environ["REQUEST_METHOD"] = method
 
+        # path may be an abs_path (including "http://host.domain.tld");
+        scheme, location, path, params, qs, frag = lib_urlparse(path)
 
-class FatalSSLAlert(Exception):
-  """Exception raised when the SSL implementation signals a fatal alert."""
-  pass
+        if frag:
+            self.simple_response("400 Bad Request",
+                                 "Illegal #fragment in Request-URI.")
+            return
 
+        if scheme:
+            environ["wsgi.url_scheme"] = scheme
+        if params:
+            path = path + ";" + params
+
+        environ["SCRIPT_NAME"] = ""
+
+        # Unquote the path+params (e.g. "/this%20path" -> "this path").
+        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
+        #
+        # But note that "...a URI must be separated into its components
+        # before the escaped characters within those components can be
+        # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
+        atoms = [urllib_unquote(x) for x in quoted_slash.split(path)]
+        path = "%2F".join(atoms)
+        environ["PATH_INFO"] = path
+
+        # Note that, like wsgiref and most other WSGI servers,
+        # we unquote the path but not the query string.
+        environ["QUERY_STRING"] = qs
+
+        # Compare request and server HTTP protocol versions, in case our
+        # server does not support the requested protocol. Limit our output
+        # to min(req, server). We want the following output:
+        #     request    server     actual written   supported response
+        #     protocol   protocol  response protocol    feature set
+        # a     1.0        1.0           1.0                1.0
+        # b     1.0        1.1           1.1                1.0
+        # c     1.1        1.0           1.0                1.0
+        # d     1.1        1.1           1.1                1.1
+        # Notice that, in (b), the response will be "HTTP/1.1" even though
+        # the client only understands 1.0. RFC 2616 10.5.6 says we should
+        # only return 505 if the _major_ version is different.
+        rp = int(req_protocol[5]), int(req_protocol[7])
+        server_protocol = environ["ACTUAL_SERVER_PROTOCOL"]
+        sp = int(server_protocol[5]), int(server_protocol[7])
+        if sp[0] != rp[0]:
+            self.simple_response("505 HTTP Version Not Supported")
+            return
+        # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol.
+        environ["SERVER_PROTOCOL"] = req_protocol
+        self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
 
-if not _fileobject_uses_str_type:
-  class CP_fileobject(socket._fileobject):
-    """Faux file object attached to a socket object."""
+        # If the Request-URI was an absoluteURI, use its location atom.
+        if location:
+            environ["SERVER_NAME"] = location
 
-    def sendall(self, data):
-      """Sendall for non-blocking sockets."""
-      while data:
+        # then all the http headers
         try:
-          bytes_sent = self.send(data)
-          data = data[bytes_sent:]
-        except socket.error as e:
-          if e.args[0] not in socket_errors_nonblocking:
-            raise
-
-    def send(self, data):
-      return self._sock.send(data)
+            self.read_headers()
+        except ValueError as ex:
+            self.simple_response("400 Bad Request", repr(ex.args))
+            return
 
-    def flush(self):
-      if self._wbuf:
-        buffer = "".join(self._wbuf)
-        self._wbuf = []
-        self.sendall(buffer)
+        mrbs = self.max_request_body_size
+        if mrbs and int(environ.get("CONTENT_LENGTH", 0)) > mrbs:
+            self.simple_response("413 Request Entity Too Large")
+            return
 
-    def recv(self, size):
-      while True:
-        try:
-          return self._sock.recv(size)
-        except socket.error as e:
-          if (e.args[0] not in socket_errors_nonblocking
-            and e.args[0] not in socket_error_eintr):
-            raise
+        # Persistent connection support
+        if self.response_protocol == "HTTP/1.1":
+            # Both server and client are HTTP/1.1
+            if environ.get("HTTP_CONNECTION", "") == "close":
+                self.close_connection = True
+        else:
+            # Either the server or client (or both) are HTTP/1.0
+            if environ.get("HTTP_CONNECTION", "") != "Keep-Alive":
+                self.close_connection = True
+
+        # Transfer-Encoding support
+        te = None
+        if self.response_protocol == "HTTP/1.1":
+            te = environ.get("HTTP_TRANSFER_ENCODING")
+            if te:
+                te = [x.strip().lower() for x in te.split(",") if x.strip()]
+
+        self.chunked_read = False
+
+        if te:
+            for enc in te:
+                if enc == "chunked":
+                    self.chunked_read = True
+                else:
+                    # Note that, even if we see "chunked", we must reject
+                    # if there is an extension we don't recognize.
+                    self.simple_response("501 Unimplemented")
+                    self.close_connection = True
+                    return
+
+        # From PEP 333:
+        # "Servers and gateways that implement HTTP 1.1 must provide
+        # transparent support for HTTP 1.1's "expect/continue" mechanism.
+        # This may be done in any of several ways:
+        #   1. Respond to requests containing an Expect: 100-continue request
+        #      with an immediate "100 Continue" response, and proceed normally.
+        #   2. Proceed with the request normally, but provide the application
+        #      with a wsgi.input stream that will send the "100 Continue"
+        #      response if/when the application first attempts to read from
+        #      the input stream. The read request must then remain blocked
+        #      until the client responds.
+        #   3. Wait until the client decides that the server does not support
+        #      expect/continue, and sends the request body on its own.
+        #      (This is suboptimal, and is not recommended.)
+        #
+        # We used to do 3, but are now doing 1. Maybe we'll do 2 someday,
+        # but it seems like it would be a big slowdown for such a rare case.
+        if environ.get("HTTP_EXPECT", "") == "100-continue":
+            self.simple_response(100)
+
+        self.ready = True
+
+    def read_headers(self):
+        """Read header lines from the incoming stream."""
+        environ = self.environ
 
-    def read(self, size=-1):
-      # Use max, disallow tiny reads in a loop as they are very inefficient.
-      # We never leave read() with any leftover data from a new recv() call
-      # in our internal buffer.
-      rbufsize = max(self._rbufsize, self.default_bufsize)
-      # Our use of StringIO rather than lists of string objects returned by
-      # recv() minimizes memory usage and fragmentation that occurs when
-      # rbufsize is large compared to the typical return value of recv().
-      buf = self._rbuf
-      buf.seek(0, 2)  # seek end
-      if size < 0:
-        # Read until EOF
-        self._rbuf = string_io()  # reset _rbuf.  we consume it via buf.
-        while True:
-          data = self.recv(rbufsize)
-          if not data:
-            break
-          buf.write(data)
-        return buf.getvalue()
-      else:
-        # Read until size bytes or EOF seen, whichever comes first
-        buf_len = buf.tell()
-        if buf_len >= size:
-          # Already have size bytes in our buffer?  Extract and return.
-          buf.seek(0)
-          rv = buf.read(size)
-          self._rbuf = string_io()
-          self._rbuf.write(buf.read())
-          return rv
-
-        self._rbuf = string_io()  # reset _rbuf.  we consume it via buf.
-        while True:
-          left = size - buf_len
-          # recv() will malloc the amount of memory given as its
-          # parameter even though it often returns much less data
-          # than that.  The returned data string is short lived
-          # as we copy it into a StringIO and free it.  This avoids
-          # fragmentation issues on many platforms.
-          data = self.recv(left)
-          if not data:
-            break
-          n = len(data)
-          if n == size and not buf_len:
-            # Shortcut.  Avoid buffer data copies when:
-            # - We have no data in our buffer.
-            # AND
-            # - Our call to recv returned exactly the
-            #   number of bytes we were asked to read.
-            return data
-          if n == left:
-            buf.write(data)
-            del data  # explicit free
-            break
-          # NOTE: (HUE-2893) This was backported from CherryPy PR
-          # #14, which fixes uploading chunked files with SSL.
-          elif n > left:
-            # Could happen with SSL transport. Differ
-            # extra data read to the next call
-            buf.write(data[:left])
-            self._rbuf.write(data[left:])
-            del data
-            break
-          buf.write(data)
-          buf_len += n
-          del data  # explicit free
-          #assert buf_len == buf.tell()
-        return buf.getvalue()
-
-    def readline(self, size=-1):
-      buf = self._rbuf
-      buf.seek(0, 2)  # seek end
-      if buf.tell() > 0:
-        # check if we already have it in our buffer
-        buf.seek(0)
-        bline = buf.readline(size)
-        if bline.endswith('\n') or len(bline) == size:
-          self._rbuf = string_io()
-          self._rbuf.write(buf.read())
-          return bline
-        del bline
-      if size < 0:
-        # Read until \n or EOF, whichever comes first
-        if self._rbufsize <= 1:
-          # Speed up unbuffered case
-          buf.seek(0)
-          buffers = [buf.read()]
-          self._rbuf = string_io()  # reset _rbuf.  we consume it via buf.
-          data = None
-          recv = self.recv
-          while data != "\n":
-            data = recv(1)
-            if not data:
-              break
-            buffers.append(data)
-          return "".join(buffers)
-
-        buf.seek(0, 2)  # seek end
-        self._rbuf = string_io()  # reset _rbuf.  we consume it via buf.
         while True:
-          data = self.recv(self._rbufsize)
-          if not data:
-            break
-          nl = data.find('\n')
-          if nl >= 0:
-            nl += 1
-            buf.write(data[:nl])
-            self._rbuf.write(data[nl:])
-            del data
-            break
-          buf.write(data)
-        return buf.getvalue()
-      else:
-        # Read until size bytes or \n or EOF seen, whichever comes first
-        buf.seek(0, 2)  # seek end
-        buf_len = buf.tell()
-        if buf_len >= size:
-          buf.seek(0)
-          rv = buf.read(size)
-          self._rbuf = string_io()
-          self._rbuf.write(buf.read())
-          return rv
-        self._rbuf = string_io()  # reset _rbuf.  we consume it via buf.
+            line = self.rfile.readline()
+            if not line:
+                # No more data--illegal end of headers
+                raise ValueError("Illegal end of headers.")
+
+            if line == '\r\n':
+                # Normal end of headers
+                break
+
+            if line[0] in ' \t':
+                # It's a continuation line.
+                v = line.strip()
+            else:
+                k, v = line.split(":", 1)
+                k, v = k.strip().upper(), v.strip()
+                envname = "HTTP_" + k.replace("-", "_")
+
+            if k in comma_separated_headers:
+                existing = environ.get(envname)
+                if existing:
+                    v = ", ".join((existing, v))
+            environ[envname] = v
+
+        ct = environ.pop("HTTP_CONTENT_TYPE", None)
+        if ct is not None:
+            environ["CONTENT_TYPE"] = ct
+        cl = environ.pop("HTTP_CONTENT_LENGTH", None)
+        if cl is not None:
+            environ["CONTENT_LENGTH"] = cl
+
+    def decode_chunked(self):
+        """Decode the 'chunked' transfer coding."""
+        cl = 0
+        data = string_io()
         while True:
-          data = self.recv(self._rbufsize)
-          if not data:
-            break
-          left = size - buf_len
-          # did we just receive a newline?
-          nl = data.find('\n', 0, left)
-          if nl >= 0:
-            nl += 1
-            # save the excess data to _rbuf
-            self._rbuf.write(data[nl:])
-            if buf_len:
-              buf.write(data[:nl])
-              break
+            line = self.rfile.readline().strip().split(";", 1)
+            chunk_size = int(line.pop(0), 16)
+            if chunk_size <= 0:
+                break
+##            if line: chunk_extension = line[0]
+            cl += chunk_size
+            data.write(self.rfile.read(chunk_size))
+            crlf = self.rfile.read(2)
+            if crlf != "\r\n":
+                self.simple_response("400 Bad Request",
+                                     "Bad chunked transfer coding "
+                                     "(expected '\\r\\n', got %r)" % crlf)
+                return
+
+        # Grab any trailer headers
+        self.read_headers()
+
+        data.seek(0)
+        self.environ["wsgi.input"] = data
+        self.environ["CONTENT_LENGTH"] = str(cl) or ""
+        return True
+
+    def respond(self):
+        """Call the appropriate WSGI app and write its iterable output."""
+        # Set rfile.maxlen to ensure we don't read past Content-Length.
+        # This will also be used to read the entire request body if errors
+        # are raised before the app can read the body.
+        if self.chunked_read:
+            # If chunked, Content-Length will be 0.
+            self.rfile.maxlen = self.max_request_body_size
+        else:
+            cl = int(self.environ.get("CONTENT_LENGTH", 0))
+            if self.max_request_body_size:
+                self.rfile.maxlen = min(cl, self.max_request_body_size)
             else:
-              # Shortcut.  Avoid data copy through buf when returning
-              # a substring of our first recv().
-              return data[:nl]
-          n = len(data)
-          if n == size and not buf_len:
-            # Shortcut.  Avoid data copy through buf when
-            # returning exactly all of our first recv().
-            return data
-          if n >= left:
-            buf.write(data[:left])
-            self._rbuf.write(data[left:])
-            break
-          buf.write(data)
-          buf_len += n
-          #assert buf_len == buf.tell()
-        return buf.getvalue()
+                self.rfile.maxlen = cl
+        self.rfile.bytes_read = 0
 
-else:
-  class CP_fileobject(socket._fileobject):
-    """Faux file object attached to a socket object."""
-
-    def sendall(self, data):
-      """Sendall for non-blocking sockets."""
-      while data:
         try:
-          bytes_sent = self.send(data)
-          data = data[bytes_sent:]
-        except socket.error as e:
-          if e.args[0] not in socket_errors_nonblocking:
-            raise
-
-    def send(self, data):
-      return self._sock.send(data)
+            self._respond()
+        except MaxSizeExceeded:
+            if not self.sent_headers:
+                self.simple_response("413 Request Entity Too Large")
+            return
 
-    def flush(self):
-      if self._wbuf:
-        buffer = "".join(self._wbuf)
-        self._wbuf = []
-        self.sendall(buffer)
+    def _respond(self):
+        if self.chunked_read:
+            if not self.decode_chunked():
+                self.close_connection = True
+                return
 
-    def recv(self, size):
-      while True:
+        response = self.wsgi_app(self.environ, self.start_response)
         try:
-          return self._sock.recv(size)
-        except socket.error as e:
-          if (e.args[0] not in socket_errors_nonblocking
-            and e.args[0] not in socket_error_eintr):
-            raise
+            for chunk in response:
+                # "The start_response callable must not actually transmit
+                # the response headers. Instead, it must store them for the
+                # server or gateway to transmit only after the first
+                # iteration of the application return value that yields
+                # a NON-EMPTY string, or upon the application's first
+                # invocation of the write() callable." (PEP 333)
+                if chunk:
+                    self.write(chunk)
+        finally:
+            if hasattr(response, "close"):
+                response.close()
+
+        if (self.ready and not self.sent_headers):
+            self.sent_headers = True
+            self.send_headers()
+        if self.chunked_write:
+            self.wfile.sendall("0\r\n\r\n")
+
+    def simple_response(self, status, msg=""):
+        """Write a simple response back to the client."""
+        status = str(status)
+        buf = ["%s %s\r\n" % (self.environ['ACTUAL_SERVER_PROTOCOL'], status),
+               "Content-Length: %s\r\n" % len(msg),
+               "Content-Type: text/plain\r\n"]
+
+        if status[:3] == "413" and self.response_protocol == 'HTTP/1.1':
+            # Request Entity Too Large
+            self.close_connection = True
+            buf.append("Connection: close\r\n")
+
+        buf.append("\r\n")
+        if msg:
+            buf.append(msg)
 
-    def read(self, size=-1):
-      if size < 0:
-        # Read until EOF
-        buffers = [self._rbuf]
-        self._rbuf = ""
-        if self._rbufsize <= 1:
-          recv_size = self.default_bufsize
+        try:
+            self.wfile.sendall("".join(buf))
+        except socket.error as x:
+            if x.args[0] not in socket_errors_to_ignore:
+                raise
+
+    def start_response(self, status, headers, exc_info = None):
+        """WSGI callable to begin the HTTP response."""
+        # "The application may call start_response more than once,
+        # if and only if the exc_info argument is provided."
+        if self.started_response and not exc_info:
+            raise AssertionError("WSGI start_response called a second "
+                                 "time with no exc_info.")
+
+        # "if exc_info is provided, and the HTTP headers have already been
+        # sent, start_response must raise an error, and should raise the
+        # exc_info tuple."
+        if self.sent_headers:
+            try:
+                raise exc_info[0], exc_info[1], exc_info[2]
+            finally:
+                exc_info = None
+
+        self.started_response = True
+        self.status = status
+        self.outheaders.extend(headers)
+        return self.write
+
+    def write(self, chunk):
+        """WSGI callable to write unbuffered data to the client.
+
+        This method is also used internally by start_response (to write
+        data from the iterable returned by the WSGI application).
+        """
+        if not self.started_response:
+            raise AssertionError("WSGI write called before start_response.")
+
+        if not self.sent_headers:
+            self.sent_headers = True
+            self.send_headers()
+
+        if self.chunked_write and chunk:
+            buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"]
+            self.wfile.sendall("".join(buf))
         else:
-          recv_size = self._rbufsize
+            self.wfile.sendall(chunk)
+
+    def send_headers(self):
+        """Assert, process, and send the HTTP response message-headers."""
+        hkeys = [key.lower() for key, value in self.outheaders]
+        status = int(self.status[:3])
+
+        if status == 413:
+            # Request Entity Too Large. Close conn to avoid garbage.
+            self.close_connection = True
+        elif "content-length" not in hkeys:
+            # "All 1xx (informational), 204 (no content),
+            # and 304 (not modified) responses MUST NOT
+            # include a message-body." So no point chunking.
+            if status < 200 or status in (204, 205, 304):
+                pass
+            else:
+                if (self.response_protocol == 'HTTP/1.1'
+                    and self.environ["REQUEST_METHOD"] != 'HEAD'):
+                    # Use the chunked transfer-coding
+                    self.chunked_write = True
+                    self.outheaders.append(("Transfer-Encoding", "chunked"))
+                else:
+                    # Closing the conn is the only way to determine len.
+                    self.close_connection = True
+
+        if "connection" not in hkeys:
+            if self.response_protocol == 'HTTP/1.1':
+                # Both server and client are HTTP/1.1 or better
+                if self.close_connection:
+                    self.outheaders.append(("Connection", "close"))
+            else:
+                # Server and/or client are HTTP/1.0
+                if not self.close_connection:
+                    self.outheaders.append(("Connection", "Keep-Alive"))
+
+        if (not self.close_connection) and (not self.chunked_read):
+            # Read any remaining request body data on the socket.
+            # "If an origin server receives a request that does not include an
+            # Expect request-header field with the "100-continue" expectation,
+            # the request includes a request body, and the server responds
+            # with a final status code before reading the entire request body
+            # from the transport connection, then the server SHOULD NOT close
+            # the transport connection until it has read the entire request,
+            # or until the client closes the connection. Otherwise, the client
+            # might not reliably receive the response message. However, this
+            # requirement is not be construed as preventing a server from
+            # defending itself against denial-of-service attacks, or from
+            # badly broken client implementations."
+            size = self.rfile.maxlen - self.rfile.bytes_read
+            if size > 0:
+                self.rfile.read(size)
+
+        if "date" not in hkeys:
+            self.outheaders.append(("Date", formatdate()))
+
+        if "server" not in hkeys:
+            self.outheaders.append(("Server", self.environ['SERVER_SOFTWARE']))
+
+        buf = [self.environ['ACTUAL_SERVER_PROTOCOL'], " ", self.status, "\r\n"]
+        try:
+            buf += [k + ": " + v + "\r\n" for k, v in self.outheaders]
+        except TypeError:
+            if not isinstance(k, str):
+                raise TypeError("WSGI response header key %r is not a string.")
+            if not isinstance(v, str):
+                raise TypeError("WSGI response header value %r is not a string.")
+            else:
+                raise
+        buf.append("\r\n")
+        self.wfile.sendall("".join(buf))
 
-        while True:
-          data = self.recv(recv_size)
-          if not data:
-            break
-          buffers.append(data)
-        return "".join(buffers)
-      else:
-        # Read until size bytes or EOF seen, whichever comes first
-        data = self._rbuf
-        buf_len = len(data)
-        if buf_len >= size:
-          self._rbuf = data[size:]
-          return data[:size]
-        buffers = []
-        if data:
-          buffers.append(data)
-        self._rbuf = ""
-        while True:
-          left = size - buf_len
-          recv_size = max(self._rbufsize, left)
-          data = self.recv(recv_size)
-          if not data:
-            break
-          buffers.append(data)
-          n = len(data)
-          if n >= left:
-            self._rbuf = data[left:]
-            buffers[-1] = data[:left]
-            break
-          buf_len += n
-        return "".join(buffers)
-
-    def readline(self, size=-1):
-      data = self._rbuf
-      if size < 0:
-        # Read until \n or EOF, whichever comes first
-        if self._rbufsize <= 1:
-          # Speed up unbuffered case
-          assert data == ""
-          buffers = []
-          while data != "\n":
-            data = self.recv(1)
-            if not data:
-              break
-            buffers.append(data)
-          return "".join(buffers)
-        nl = data.find('\n')
-        if nl >= 0:
-          nl += 1
-          self._rbuf = data[nl:]
-          return data[:nl]
-        buffers = []
-        if data:
-          buffers.append(data)
-        self._rbuf = ""
-        while True:
-          data = self.recv(self._rbufsize)
-          if not data:
-            break
-          buffers.append(data)
-          nl = data.find('\n')
-          if nl >= 0:
-            nl += 1
-            self._rbuf = data[nl:]
-            buffers[-1] = data[:nl]
-            break
-        return "".join(buffers)
-      else:
-        # Read until size bytes or \n or EOF seen, whichever comes first
-        nl = data.find('\n', 0, size)
-        if nl >= 0:
-          nl += 1
-          self._rbuf = data[nl:]
-          return data[:nl]
-        buf_len = len(data)
-        if buf_len >= size:
-          self._rbuf = data[size:]
-          return data[:size]
-        buffers = []
-        if data:
-          buffers.append(data)
-        self._rbuf = ""
-        while True:
-          data = self.recv(self._rbufsize)
-          if not data:
-            break
-          buffers.append(data)
-          left = size - buf_len
-          nl = data.find('\n', 0, left)
-          if nl >= 0:
-            nl += 1
-            self._rbuf = data[nl:]
-            buffers[-1] = data[:nl]
-            break
-          n = len(data)
-          if n >= left:
-            self._rbuf = data[left:]
-            buffers[-1] = data[:left]
-            break
-          buf_len += n
-        return "".join(buffers)
 
+class NoSSLError(Exception):
+    """Exception raised when a client speaks HTTP to an HTTPS socket."""
+    pass
 
-class SSL_fileobject(CP_fileobject):
-  """SSL file object attached to a socket object."""
 
-  ssl_timeout = 3
-  ssl_retry = .01
+class FatalSSLAlert(Exception):
+    """Exception raised when the SSL implementation signals a fatal alert."""
+    pass
 
-  def _safe_call(self, is_reader, call, *args, **kwargs):
-    """Wrap the given call with SSL error-trapping.
 
-    is_reader: if False EOF errors will be raised. If True, EOF errors
-      will return "" (to emulate normal sockets).
-    """
-    start = time.time()
-    while True:
-      try:
-        return call(*args, **kwargs)
-      except SSL.WantReadError:
-        # Sleep and try again. This is dangerous, because it means
-        # the rest of the stack has no way of differentiating
-        # between a "new handshake" error and "client dropped".
-        # Note this isn't an endless loop: there's a timeout below.
-        time.sleep(self.ssl_retry)
-      except SSL.WantWriteError:
-        time.sleep(self.ssl_retry)
-      except SSL.SysCallError as e:
-        if is_reader and e.args == (-1, 'Unexpected EOF'):
-          return ""
-
-        errnum = e.args[0]
-        if is_reader and errnum in socket_errors_to_ignore:
-          return ""
-        raise socket.error(errnum)
-      except SSL.Error as e:
-        if is_reader and e.args == (-1, 'Unexpected EOF'):
-          return ""
-
-        thirdarg = None
-        try:
-          thirdarg = e.args[0][0][2]
-        except IndexError:
-          pass
+if not _fileobject_uses_str_type:
+    class CP_fileobject(socket._fileobject):
+        """Faux file object attached to a socket object."""
+
+        def sendall(self, data):
+            """Sendall for non-blocking sockets."""
+            while data:
+                try:
+                    bytes_sent = self.send(data)
+                    data = data[bytes_sent:]
+                except socket.error as e:
+                    if e.args[0] not in socket_errors_nonblocking:
+                        raise
+
+        def send(self, data):
+            return self._sock.send(data)
+
+        def flush(self):
+            if self._wbuf:
+                buffer = "".join(self._wbuf)
+                self._wbuf = []
+                self.sendall(buffer)
+
+        def recv(self, size):
+            while True:
+                try:
+                    return self._sock.recv(size)
+                except socket.error as e:
+                    if (e.args[0] not in socket_errors_nonblocking
+                        and e.args[0] not in socket_error_eintr):
+                        raise
+
+        def read(self, size=-1):
+            # Use max, disallow tiny reads in a loop as they are very inefficient.
+            # We never leave read() with any leftover data from a new recv() call
+            # in our internal buffer.
+            rbufsize = max(self._rbufsize, self.default_bufsize)
+            # Our use of StringIO rather than lists of string objects returned by
+            # recv() minimizes memory usage and fragmentation that occurs when
+            # rbufsize is large compared to the typical return value of recv().
+            buf = self._rbuf
+            buf.seek(0, 2)  # seek end
+            if size < 0:
+                # Read until EOF
+                self._rbuf = string_io()  # reset _rbuf.  we consume it via buf.
+                while True:
+                    data = self.recv(rbufsize)
+                    if not data:
+                        break
+                    buf.write(data)
+                return buf.getvalue()
+            else:
+                # Read until size bytes or EOF seen, whichever comes first
+                buf_len = buf.tell()
+                if buf_len >= size:
+                    # Already have size bytes in our buffer?  Extract and return.
+                    buf.seek(0)
+                    rv = buf.read(size)
+                    self._rbuf = string_io()
+                    self._rbuf.write(buf.read())
+                    return rv
+
+                self._rbuf = string_io()  # reset _rbuf.  we consume it via buf.
+                while True:
+                    left = size - buf_len
+                    # recv() will malloc the amount of memory given as its
+                    # parameter even though it often returns much less data
+                    # than that.  The returned data string is short lived
+                    # as we copy it into a StringIO and free it.  This avoids
+                    # fragmentation issues on many platforms.
+                    data = self.recv(left)
+                    if not data:
+                        break
+                    n = len(data)
+                    if n == size and not buf_len:
+                        # Shortcut.  Avoid buffer data copies when:
+                        # - We have no data in our buffer.
+                        # AND
+                        # - Our call to recv returned exactly the
+                        #   number of bytes we were asked to read.
+                        return data
+                    if n == left:
+                        buf.write(data)
+                        del data  # explicit free
+                        break
+                    # NOTE: (HUE-2893) This was backported from CherryPy PR
+                    # #14, which fixes uploading chunked files with SSL.
+                    elif n > left:
+                        # Could happen with SSL transport. Differ
+                        # extra data read to the next call
+                        buf.write(data[:left])
+                        self._rbuf.write(data[left:])
+                        del data
+                        break
+                    buf.write(data)
+                    buf_len += n
+                    del data  # explicit free
+                    #assert buf_len == buf.tell()
+                return buf.getvalue()
+
+        def readline(self, size=-1):
+            buf = self._rbuf
+            buf.seek(0, 2)  # seek end
+            if buf.tell() > 0:
+                # check if we already have it in our buffer
+                buf.seek(0)
+                bline = buf.readline(size)
+                if bline.endswith('\n') or len(bline) == size:
+                    self._rbuf = string_io()
+                    self._rbuf.write(buf.read())
+                    return bline
+                del bline
+            if size < 0:
+                # Read until \n or EOF, whichever comes first
+                if self._rbufsize <= 1:
+                    # Speed up unbuffered case
+                    buf.seek(0)
+                    buffers = [buf.read()]
+                    self._rbuf = string_io()  # reset _rbuf.  we consume it via buf.
+                    data = None
+                    recv = self.recv
+                    while data != "\n":
+                        data = recv(1)
+                        if not data:
+                            break
+                        buffers.append(data)
+                    return "".join(buffers)
+
+                buf.seek(0, 2)  # seek end
+                self._rbuf = string_io()  # reset _rbuf.  we consume it via buf.
+                while True:
+                    data = self.recv(self._rbufsize)
+                    if not data:
+                        break
+                    nl = data.find('\n')
+                    if nl >= 0:
+                        nl += 1
+                        buf.write(data[:nl])
+                        self._rbuf.write(data[nl:])
+                        del data
+                        break
+                    buf.write(data)
+                return buf.getvalue()
+            else:
+                # Read until size bytes or \n or EOF seen, whichever comes first
+                buf.seek(0, 2)  # seek end
+                buf_len = buf.tell()
+                if buf_len >= size:
+                    buf.seek(0)
+                    rv = buf.read(size)
+                    self._rbuf = string_io()
+                    self._rbuf.write(buf.read())
+                    return rv
+                self._rbuf = string_io()  # reset _rbuf.  we consume it via buf.
+                while True:
+                    data = self.recv(self._rbufsize)
+                    if not data:
+                        break
+                    left = size - buf_len
+                    # did we just receive a newline?
+                    nl = data.find('\n', 0, left)
+                    if nl >= 0:
+                        nl += 1
+                        # save the excess data to _rbuf
+                        self._rbuf.write(data[nl:])
+                        if buf_len:
+                            buf.write(data[:nl])
+                            break
+                        else:
+                            # Shortcut.  Avoid data copy through buf when returning
+                            # a substring of our first recv().
+                            return data[:nl]
+                    n = len(data)
+                    if n == size and not buf_len:
+                        # Shortcut.  Avoid data copy through buf when
+                        # returning exactly all of our first recv().
+                        return data
+                    if n >= left:
+                        buf.write(data[:left])
+                        self._rbuf.write(data[left:])
+                        break
+                    buf.write(data)
+                    buf_len += n
+                    #assert buf_len == buf.tell()
+                return buf.getvalue()
 
-        if thirdarg == 'http request':
-          # The client is talking HTTP to an HTTPS server.
-          raise NoSSLError()
-        raise FatalSSLAlert(*e.args)
+else:
+    class CP_fileobject(socket._fileobject):
+        """Faux file object attached to a socket object."""
+
+        def sendall(self, data):
+            """Sendall for non-blocking sockets."""
+            while data:
+                try:
+                    bytes_sent = self.send(data)
+                    data = data[bytes_sent:]
+                except socket.error as e:
+                    if e.args[0] not in socket_errors_nonblocking:
+                        raise
+
+        def send(self, data):
+            return self._sock.send(data)
+
+        def flush(self):
+            if self._wbuf:
+                buffer = "".join(self._wbuf)
+                self._wbuf = []
+                self.sendall(buffer)
+
+        def recv(self, size):
+            while True:
+                try:
+                    return self._sock.recv(size)
+                except socket.error as e:
+                    if (e.args[0] not in socket_errors_nonblocking
+                        and e.args[0] not in socket_error_eintr):
+                        raise
+
+        def read(self, size=-1):
+            if size < 0:
+                # Read until EOF
+                buffers = [self._rbuf]
+                self._rbuf = ""
+                if self._rbufsize <= 1:
+                    recv_size = self.default_bufsize
+                else:
+                    recv_size = self._rbufsize
+
+                while True:
+                    data = self.recv(recv_size)
+                    if not data:
+                        break
+                    buffers.append(data)
+                return "".join(buffers)
+            else:
+                # Read until size bytes or EOF seen, whichever comes first
+                data = self._rbuf
+                buf_len = len(data)
+                if buf_len >= size:
+                    self._rbuf = data[size:]
+                    return data[:size]
+                buffers = []
+                if data:
+                    buffers.append(data)
+                self._rbuf = ""
+                while True:
+                    left = size - buf_len
+                    recv_size = max(self._rbufsize, left)
+                    data = self.recv(recv_size)
+                    if not data:
+                        break
+                    buffers.append(data)
+                    n = len(data)
+                    if n >= left:
+                        self._rbuf = data[left:]
+                        buffers[-1] = data[:left]
+                        break
+                    buf_len += n
+                return "".join(buffers)
+
+        def readline(self, size=-1):
+            data = self._rbuf
+            if size < 0:
+                # Read until \n or EOF, whichever comes first
+                if self._rbufsize <= 1:
+                    # Speed up unbuffered case
+                    assert data == ""
+                    buffers = []
+                    while data != "\n":
+                        data = self.recv(1)
+                        if not data:
+                            break
+                        buffers.append(data)
+                    return "".join(buffers)
+                nl = data.find('\n')
+                if nl >= 0:
+                    nl += 1
+                    self._rbuf = data[nl:]
+                    return data[:nl]
+                buffers = []
+                if data:
+                    buffers.append(data)
+                self._rbuf = ""
+                while True:
+                    data = self.recv(self._rbufsize)
+                    if not data:
+                        break
+                    buffers.append(data)
+                    nl = data.find('\n')
+                    if nl >= 0:
+                        nl += 1
+                        self._rbuf = data[nl:]
+                        buffers[-1] = data[:nl]
+                        break
+                return "".join(buffers)
+            else:
+                # Read until size bytes or \n or EOF seen, whichever comes first
+                nl = data.find('\n', 0, size)
+                if nl >= 0:
+                    nl += 1
+                    self._rbuf = data[nl:]
+                    return data[:nl]
+                buf_len = len(data)
+                if buf_len >= size:
+                    self._rbuf = data[size:]
+                    return data[:size]
+                buffers = []
+                if data:
+                    buffers.append(data)
+                self._rbuf = ""
+                while True:
+                    data = self.recv(self._rbufsize)
+                    if not data:
+                        break
+                    buffers.append(data)
+                    left = size - buf_len
+                    nl = data.find('\n', 0, left)
+                    if nl >= 0:
+                        nl += 1
+                        self._rbuf = data[nl:]
+                        buffers[-1] = data[:nl]
+                        break
+                    n = len(data)
+                    if n >= left:
+                        self._rbuf = data[left:]
+                        buffers[-1] = data[:left]
+                        break
+                    buf_len += n
+                return "".join(buffers)
 
-      if time.time() - start > self.ssl_timeout:
-        raise socket.timeout("timed out")
 
-  def recv(self, *args, **kwargs):
-    buf = []
-    r = super(SSL_fileobject, self).recv
-    while True:
-      data = self._safe_call(True, r, *args, **kwargs)
-      buf.append(data)
-      p = self._sock.pending()
-      if not p:
-        return "".join(buf)
+class SSL_fileobject(CP_fileobject):
+    """SSL file object attached to a socket object."""
 
-  def sendall(self, *args, **kwargs):
-    return self._safe_call(False, super(SSL_fileobject, self).sendall, *args, **kwargs)
+    ssl_timeout = 3
+    ssl_retry = .01
 
-  def send(self, *args, **kwargs):
-    return self._safe_call(False, super(SSL_fileobject, self).send, *args, **kwargs)
+    def _safe_call(self, is_reader, call, *args, **kwargs):
+        """Wrap the given call with SSL error-trapping.
+
+        is_reader: if False EOF errors will be raised. If True, EOF errors
+            will return "" (to emulate normal sockets).
+        """
+        start = time.time()
+        while True:
+            try:
+                return call(*args, **kwargs)
+            except SSL.WantReadError:
+                # Sleep and try again. This is dangerous, because it means
+                # the rest of the stack has no way of differentiating
+                # between a "new handshake" error and "client dropped".
+                # Note this isn't an endless loop: there's a timeout below.
+                time.sleep(self.ssl_retry)
+            except SSL.WantWriteError:
+                time.sleep(self.ssl_retry)
+            except SSL.SysCallError as e:
+                if is_reader and e.args == (-1, 'Unexpected EOF'):
+                    return ""
+
+                errnum = e.args[0]
+                if is_reader and errnum in socket_errors_to_ignore:
+                    return ""
+                raise socket.error(errnum)
+            except SSL.Error as e:
+                if is_reader and e.args == (-1, 'Unexpected EOF'):
+                    return ""
+
+                thirdarg = None
+                try:
+                    thirdarg = e.args[0][0][2]
+                except IndexError:
+                    pass
+
+                if thirdarg == 'http request':
+                    # The client is talking HTTP to an HTTPS server.
+                    raise NoSSLError()
+                raise FatalSSLAlert(*e.args)
+
+            if time.time() - start > self.ssl_timeout:
+                raise socket.timeout("timed out")
+
+    def recv(self, *args, **kwargs):
+        buf = []
+        r = super(SSL_fileobject, self).recv
+        while True:
+            data = self._safe_call(True, r, *args, **kwargs)
+            buf.append(data)
+            p = self._sock.pending()
+            if not p:
+                return "".join(buf)
+
+    def sendall(self, *args, **kwargs):
+        return self._safe_call(False, super(SSL_fileobject, self).sendall, *args, **kwargs)
+
+    def send(self, *args, **kwargs):
+        return self._safe_call(False, super(SSL_fileobject, self).send, *args, **kwargs)
 
 
 class HTTPConnection(object):
-  """An HTTP connection (active socket).
-
-  socket: the raw socket object (usually TCP) for this connection.
-  wsgi_app: the WSGI application for this server/connection.
-  environ: a WSGI environ template. This will be copied for each request.
-
-  rfile: a fileobject for reading from the socket.
-  send: a function for writing (+ flush) to the socket.
-  """
-
-  rbufsize = -1
-  RequestHandlerClass = HTTPRequest
-  environ = {"wsgi.version": (1, 0),
-         "wsgi.url_scheme": "http",
-         "wsgi.multithread": True,
-         "wsgi.multiprocess": False,
-         "wsgi.run_once": False,
-         "wsgi.errors": sys.stderr,
-         }
-
-  def __init__(self, sock, wsgi_app, environ):
-    self.socket = sock
-    self.wsgi_app = wsgi_app
-
-    # Copy the class environ into self.
-    self.environ = self.environ.copy()
-    self.environ.update(environ)
-
-    if SSL and isinstance(sock, SSL.ConnectionType):
-      timeout = sock.gettimeout()
-      self.rfile = SSL_fileobject(sock, "rb", self.rbufsize)
-      self.rfile.ssl_timeout = timeout
-      self.wfile = SSL_fileobject(sock, "wb", -1)
-      self.wfile.ssl_timeout = timeout
-    else:
-      self.rfile = CP_fileobject(sock, "rb", self.rbufsize)
-      self.wfile = CP_fileobject(sock, "wb", -1)
+    """An HTTP connection (active socket).
 
-    # Wrap wsgi.input but not HTTPConnection.rfile itself.
-    # We're also not setting maxlen yet; we'll do that separately
-    # for headers and body for each iteration of self.communicate
-    # (if maxlen is 0 the wrapper doesn't check length).
-    self.environ["wsgi.input"] = SizeCheckWrapper(self.rfile, 0)
+    socket: the raw socket object (usually TCP) for this connection.
+    wsgi_app: the WSGI application for this server/connection.
+    environ: a WSGI environ template. This will be copied for each request.
 
-  def communicate(self):
-    """Read each request and respond appropriately."""
-    try:
-      while True:
-        # (re)set req to None so that if something goes wrong in
-        # the RequestHandlerClass constructor, the error doesn't
-        # get written to the previous request.
-        req = None
-        req = self.RequestHandlerClass(self.wfile, self.environ,
-                         self.wsgi_app)
-
-        # This order of operations should guarantee correct pipelining.
-        req.parse_request()
-        if not req.ready:
-          # Something went wrong in the parsing (and the server has
-          # probably already made a simple_response). Return and
-          # let the conn close.
-          return
-
-        req.respond()
-        if req.close_connection:
-          return
-
-    except socket.error as e:
-      errnum = e.args[0]
-      if errnum == 'timed out':
-        # Don't send a 408 if there is no outstanding request; only
-        # if we're in the middle of a request.
-        # See http://www.cherrypy.org/ticket/853
-        if req and req.started_request and not req.sent_headers:
-          req.simple_response("408 Request Timeout")
-      elif errnum not in socket_errors_to_ignore:
-        if req and not req.sent_headers:
-          req.simple_response("500 Internal Server Error",
-                    format_exc())
-      return
-    except (KeyboardInterrupt, SystemExit):
-      raise
-    except FatalSSLAlert as e:
-      # Close the connection.
-      return
-    except NoSSLError:
-      if req and not req.sent_headers:
-        # Unwrap our wfile
-        req.wfile = CP_fileobject(self.socket._sock, "wb", -1)
-        req.simple_response("400 Bad Request",
-          "The client sent a plain HTTP request, but "
-          "this server only speaks HTTPS on this port.")
-        self.linger = True
-    except Exception as e:
-      if req and not req.sent_headers:
-        req.simple_response("500 Internal Server Error", format_exc())
-
-  linger = False
-
-  def close(self):
-    """Close the socket underlying this connection."""
-    self.rfile.close()
-
-    if not self.linger:
-      # Python's socket module does NOT call close on the kernel socket
-      # when you call socket.close(). We do so manually here because we
-      # want this server to send a FIN TCP segment immediately. Note this
-      # must be called *before* calling socket.close(), because the latter
-      # drops its reference to the kernel socket.
-      self.socket._sock.close()
-      self.socket.close()
-    else:
-      # On the other hand, sometimes we want to hang around for a bit
-      # to make sure the client has a chance to read our entire
-      # response. Skipping the close() calls here delays the FIN
-      # packet until the socket object is garbage-collected later.
-      # Someday, perhaps, we'll do the full lingering_close that
-      # Apache does, but not today.
-      pass
+    rfile: a fileobject for reading from the socket.
+    send: a function for writing (+ flush) to the socket.
+    """
+
+    rbufsize = -1
+    RequestHandlerClass = HTTPRequest
+    environ = {"wsgi.version": (1, 0),
+               "wsgi.url_scheme": "http",
+               "wsgi.multithread": True,
+               "wsgi.multiprocess": False,
+               "wsgi.run_once": False,
+               "wsgi.errors": sys.stderr,
+               }
+
+    def __init__(self, sock, wsgi_app, environ):
+        self.socket = sock
+        self.wsgi_app = wsgi_app
+
+        # Copy the class environ into self.
+        self.environ = self.environ.copy()
+        self.environ.update(environ)
+
+        if SSL and isinstance(sock, SSL.ConnectionType):
+            timeout = sock.gettimeout()
+            self.rfile = SSL_fileobject(sock, "rb", self.rbufsize)
+            self.rfile.ssl_timeout = timeout
+            self.wfile = SSL_fileobject(sock, "wb", -1)
+            self.wfile.ssl_timeout = timeout
+        else:
+            self.rfile = CP_fileobject(sock, "rb", self.rbufsize)
+            self.wfile = CP_fileobject(sock, "wb", -1)
+
+        # Wrap wsgi.input but not HTTPConnection.rfile itself.
+        # We're also not setting maxlen yet; we'll do that separately
+        # for headers and body for each iteration of self.communicate
+        # (if maxlen is 0 the wrapper doesn't check length).
+        self.environ["wsgi.input"] = SizeCheckWrapper(self.rfile, 0)
+
+    def communicate(self):
+        """Read each request and respond appropriately."""
+        try:
+            while True:
+                # (re)set req to None so that if something goes wrong in
+                # the RequestHandlerClass constructor, the error doesn't
+                # get written to the previous request.
+                req = None
+                req = self.RequestHandlerClass(self.wfile, self.environ,
+                                               self.wsgi_app)
+
+                # This order of operations should guarantee correct pipelining.
+                req.parse_request()
+                if not req.ready:
+                    # Something went wrong in the parsing (and the server has
+                    # probably already made a simple_response). Return and
+                    # let the conn close.
+                    return
+
+                req.respond()
+                if req.close_connection:
+                    return
+
+        except socket.error as e:
+            errnum = e.args[0]
+            if errnum == 'timed out':
+                # Don't send a 408 if there is no outstanding request; only
+                # if we're in the middle of a request.
+                # See http://www.cherrypy.org/ticket/853
+                if req and req.started_request and not req.sent_headers:
+                    req.simple_response("408 Request Timeout")
+            elif errnum not in socket_errors_to_ignore:
+                if req and not req.sent_headers:
+                    req.simple_response("500 Internal Server Error",
+                                        format_exc())
+            return
+        except (KeyboardInterrupt, SystemExit):
+            raise
+        except FatalSSLAlert as e:
+            # Close the connection.
+            return
+        except NoSSLError:
+            if req and not req.sent_headers:
+                # Unwrap our wfile
+                req.wfile = CP_fileobject(self.socket._sock, "wb", -1)
+                req.simple_response("400 Bad Request",
+                    "The client sent a plain HTTP request, but "
+                    "this server only speaks HTTPS on this port.")
+                self.linger = True
+        except Exception as e:
+            if req and not req.sent_headers:
+                req.simple_response("500 Internal Server Error", format_exc())
+
+    linger = False
+
+    def close(self):
+        """Close the socket underlying this connection."""
+        self.rfile.close()
+
+        if not self.linger:
+            # Python's socket module does NOT call close on the kernel socket
+            # when you call socket.close(). We do so manually here because we
+            # want this server to send a FIN TCP segment immediately. Note this
+            # must be called *before* calling socket.close(), because the latter
+            # drops its reference to the kernel socket.
+            self.socket._sock.close()
+            self.socket.close()
+        else:
+            # On the other hand, sometimes we want to hang around for a bit
+            # to make sure the client has a chance to read our entire
+            # response. Skipping the close() calls here delays the FIN
+            # packet until the socket object is garbage-collected later.
+            # Someday, perhaps, we'll do the full lingering_close that
+            # Apache does, but not today.
+            pass
 
 
 def format_exc(limit=None):
-  """Like print_exc() but return a string. Backport for Python 2.3."""
-  try:
-    etype, value, tb = sys.exc_info()
-    return ''.join(traceback.format_exception(etype, value, tb, limit))
-  finally:
-    etype = value = tb = None
+    """Like print_exc() but return a string. Backport for Python 2.3."""
+    try:
+        etype, value, tb = sys.exc_info()
+        return ''.join(traceback.format_exception(etype, value, tb, limit))
+    finally:
+        etype = value = tb = None
 
 
 _SHUTDOWNREQUEST = None
 
 class WorkerThread(threading.Thread):
-  """Thread which continuously polls a Queue for Connection objects.
+    """Thread which continuously polls a Queue for Connection objects.
 
-  server: the HTTP Server which spawned this thread, and which owns the
-    Queue and is placing active connections into it.
-  ready: a simple flag for the calling server to know when this thread
-    has begun polling the Queue.
+    server: the HTTP Server which spawned this thread, and which owns the
+        Queue and is placing active connections into it.
+    ready: a simple flag for the calling server to know when this thread
+        has begun polling the Queue.
 
-  Due to the timing issues of polling a Queue, a WorkerThread does not
-  check its own 'ready' flag after it has started. To stop the thread,
-  it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
-  (one for each running WorkerThread).
-  """
+    Due to the timing issues of polling a Queue, a WorkerThread does not
+    check its own 'ready' flag after it has started. To stop the thread,
+    it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
+    (one for each running WorkerThread).
+    """
 
-  conn = None
+    conn = None
 
-  def __init__(self, server):
-    self.ready = False
-    self.server = server
-    threading.Thread.__init__(self)
+    def __init__(self, server):
+        self.ready = False
+        self.server = server
+        threading.Thread.__init__(self)
 
-  def run(self):
-    try:
-      self.ready = True
-      while True:
+    def run(self):
         try:
-          conn = self.server.requests.get()
-          if conn is _SHUTDOWNREQUEST:
+            self.ready = True
+            while True:
+                try:
+                    conn = self.server.requests.get()
+                    if conn is _SHUTDOWNREQUEST:
+                        return
+
+                    self.conn = conn
+                    try:
+                        conn.communicate()
+                    finally:
+                        conn.close()
+                        self.conn = None
+                except Exception as ex:
+                    LOG.exception('WSGI (%s) error: %s' % (self, ex))
+        except (KeyboardInterrupt, SystemExit) as exc:
+            self.server.interrupt = exc
             return
 
-          self.conn = conn
-          try:
-            conn.communicate()
-          finally:
-            conn.close()
-            self.conn = None
-        except Exception as ex:
-          LOG.exception('WSGI (%s) error: %s' % (self, ex))
-    except (KeyboardInterrupt, SystemExit) as exc:
-      self.server.interrupt = exc
-      return
-
 
 class ThreadPool(object):
-  """A Request Queue for the CherryPyWSGIServer which pools threads.
-
-  ThreadPool objects must provide min, get(), put(obj), start()
-  and stop(timeout) attributes.
-  """
-
-  def __init__(self, server, min=10, max=-1):
-    self.server = server
-    self.min = min
-    self.max = max
-    self._threads = []
-    self._queue = queue.Queue()
-    self.get = self._queue.get
-
-  def start(self):
-    """Start the pool of threads."""
-    for i in range(self.min):
-      self._threads.append(WorkerThread(self.server))
-    for worker in self._threads:
-      worker.setName("CP WSGIServer " + worker.getName())
-      worker.start()
-    for worker in self._threads:
-      while not worker.ready:
-        time.sleep(.1)
-
-  def _get_idle(self):
-    """Number of worker threads which are idle. Read-only."""
-    return len([t for t in self._threads if t.conn is None])
-  idle = property(_get_idle, doc=_get_idle.__doc__)
-
-  def put(self, obj):
-    self._queue.put(obj)
-    if obj is _SHUTDOWNREQUEST:
-      return
-
-  def grow(self, amount):
-    """Spawn new worker threads (not above self.max)."""
-    for i in range(amount):
-      if self.max > 0 and len(self._threads) >= self.max:
-        break
-      worker = WorkerThread(self.server)
-      worker.setName("CP WSGIServer " + worker.getName())
-      self._threads.append(worker)
-      worker.start()
-
-  def shrink(self, amount):
-    """Kill off worker threads (not below self.min)."""
-    # Grow/shrink the pool if necessary.
-    # Remove any dead threads from our list
-    for t in self._threads:
-      if not t.isAlive():
-        self._threads.remove(t)
-        amount -= 1
-
-    if amount > 0:
-      for i in range(min(amount, len(self._threads) - self.min)):
-        # Put a number of shutdown requests on the queue equal
-        # to 'amount'. Once each of those is processed by a worker,
-        # that worker will terminate and be culled from our list
-        # in self.put.
-        self._queue.put(_SHUTDOWNREQUEST)
-
-  def stop(self, timeout=5):
-    # Must shut down threads here so the code that calls
-    # this method can know when all threads are stopped.
-    for worker in self._threads:
-      self._queue.put(_SHUTDOWNREQUEST)
-
-    # Don't join currentThread (when stop is called inside a request).
-    current = threading.currentThread()
-    while self._threads:
-      worker = self._threads.pop()
-      if worker is not current and worker.isAlive():
-        try:
-          if timeout is None or timeout < 0:
-            worker.join()
-          else:
-            worker.join(timeout)
-            if worker.isAlive():
-              # We exhausted the timeout.
-              # Forcibly shut down the socket.
-              c = worker.conn
-              if c and not c.rfile.closed:
-                if SSL and isinstance(c.socket, SSL.ConnectionType):
-                  # pyOpenSSL.socket.shutdown takes no args
-                  c.socket.shutdown()
-                else:
-                  c.socket.shutdown(socket.SHUT_RD)
-              worker.join()
-        except (AssertionError,
-            # Ignore repeated Ctrl-C.
-            # See http://www.cherrypy.org/ticket/691.
-            KeyboardInterrupt) as exc1:
-          pass
+    """A Request Queue for the CherryPyWSGIServer which pools threads.
+
+    ThreadPool objects must provide min, get(), put(obj), start()
+    and stop(timeout) attributes.
+    """
+
+    def __init__(self, server, min=10, max=-1):
+        self.server = server
+        self.min = min
+        self.max = max
+        self._threads = []
+        self._queue = queue.Queue()
+        self.get = self._queue.get
+
+    def start(self):
+        """Start the pool of threads."""
+        for i in range(self.min):
+            self._threads.append(WorkerThread(self.server))
+        for worker in self._threads:
+            worker.setName("CP WSGIServer " + worker.getName())
+            worker.start()
+        for worker in self._threads:
+            while not worker.ready:
+                time.sleep(.1)
+
+    def _get_idle(self):
+        """Number of worker threads which are idle. Read-only."""
+        return len([t for t in self._threads if t.conn is None])
+    idle = property(_get_idle, doc=_get_idle.__doc__)
+
+    def put(self, obj):
+        self._queue.put(obj)
+        if obj is _SHUTDOWNREQUEST:
+            return
+
+    def grow(self, amount):
+        """Spawn new worker threads (not above self.max)."""
+        for i in range(amount):
+            if self.max > 0 and len(self._threads) >= self.max:
+                break
+            worker = WorkerThread(self.server)
+            worker.setName("CP WSGIServer " + worker.getName())
+            self._threads.append(worker)
+            worker.start()
+
+    def shrink(self, amount):
+        """Kill off worker threads (not below self.min)."""
+        # Grow/shrink the pool if necessary.
+        # Remove any dead threads from our list
+        for t in self._threads:
+            if not t.isAlive():
+                self._threads.remove(t)
+                amount -= 1
+
+        if amount > 0:
+            for i in range(min(amount, len(self._threads) - self.min)):
+                # Put a number of shutdown requests on the queue equal
+                # to 'amount'. Once each of those is processed by a worker,
+                # that worker will terminate and be culled from our list
+                # in self.put.
+                self._queue.put(_SHUTDOWNREQUEST)
+
+    def stop(self, timeout=5):
+        # Must shut down threads here so the code that calls
+        # this method can know when all threads are stopped.
+        for worker in self._threads:
+            self._queue.put(_SHUTDOWNREQUEST)
+
+        # Don't join currentThread (when stop is called inside a request).
+        current = threading.currentThread()
+        while self._threads:
+            worker = self._threads.pop()
+            if worker is not current and worker.isAlive():
+                try:
+                    if timeout is None or timeout < 0:
+                        worker.join()
+                    else:
+                        worker.join(timeout)
+                        if worker.isAlive():
+                            # We exhausted the timeout.
+                            # Forcibly shut down the socket.
+                            c = worker.conn
+                            if c and not c.rfile.closed:
+                                if SSL and isinstance(c.socket, SSL.ConnectionType):
+                                    # pyOpenSSL.socket.shutdown takes no args
+                                    c.socket.shutdown()
+                                else:
+                                    c.socket.shutdown(socket.SHUT_RD)
+                            worker.join()
+                except (AssertionError,
+                        # Ignore repeated Ctrl-C.
+                        # See http://www.cherrypy.org/ticket/691.
+                        KeyboardInterrupt) as exc1:
+                    pass
 
 
 
 class SSLConnection(object):
-  """A thread-safe wrapper for an SSL.Connection.
-
-  *args: the arguments to create the wrapped SSL.Connection(*args).
-  """
-
-  def __init__(self, *args):
-    self._ssl_conn = SSL.Connection(*args)
-    self._lock = threading.RLock()
-
-  for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
-        'renegotiate', 'bind', 'listen', 'connect', 'accept',
-        'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list',
-        'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
-        'makefile', 'get_app_data', 'set_app_data', 'state_string',
-        'sock_shutdown', 'get_peer_certificate', 'want_read',
-        'want_write', 'set_connect_state', 'set_accept_state',
-        'connect_ex', 'sendall', 'settimeout'):
-    exec("""def %s(self, *args):
-    self._lock.acquire()
-    try:
-      return self._ssl_conn.%s(*args)
-    finally:
-      self._lock.release()
+    """A thread-safe wrapper for an SSL.Connection.
+
+    *args: the arguments to create the wrapped SSL.Connection(*args).
+    """
+
+    def __init__(self, *args):
+        self._ssl_conn = SSL.Connection(*args)
+        self._lock = threading.RLock()
+
+    for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
+              'renegotiate', 'bind', 'listen', 'connect', 'accept',
+              'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list',
+              'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
+              'makefile', 'get_app_data', 'set_app_data', 'state_string',
+              'sock_shutdown', 'get_peer_certificate', 'want_read',
+              'want_write', 'set_connect_state', 'set_accept_state',
+              'connect_ex', 'sendall', 'settimeout'):
+        exec("""def %s(self, *args):
+        self._lock.acquire()
+        try:
+            return self._ssl_conn.%s(*args)
+        finally:
+            self._lock.release()
 """ % (f, f))
 
 
 try:
-  import fcntl
+    import fcntl
 except ImportError:
-  try:
-    from ctypes import windll, WinError
-  except ImportError:
-    def prevent_socket_inheritance(sock):
-      """Dummy function, since neither fcntl nor ctypes are available."""
-      pass
-  else:
-    def prevent_socket_inheritance(sock):
-      """Mark the given socket fd as non-inheritable (Windows)."""
-      if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0):
-        raise WinError()
+    try:
+        from ctypes import windll, WinError
+    except ImportError:
+        def prevent_socket_inheritance(sock):
+            """Dummy function, since neither fcntl nor ctypes are available."""
+            pass
+    else:
+        def prevent_socket_inheritance(sock):
+            """Mark the given socket fd as non-inheritable (Windows)."""
+            if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0):
+                raise WinError()
 else:
-  def prevent_socket_inheritance(sock):
-    """Mark the given socket fd as non-inheritable (POSIX)."""
-    fd = sock.fileno()
-    old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
-    fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
+    def prevent_socket_inheritance(sock):
+        """Mark the given socket fd as non-inheritable (POSIX)."""
+        fd = sock.fileno()
+        old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
+        fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
 
 
 class CherryPyWSGIServer(object):
-  """An HTTP server for WSGI.
-
-  bind_addr: The interface on which to listen for connections.
-    For TCP sockets, a (host, port) tuple. Host values may be any IPv4
-    or IPv6 address, or any valid hostname. The string 'localhost' is a
-    synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
-    The string '0.0.0.0' is a special IPv4 entry meaning "any active
-    interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
-    IPv6. The empty string or None are not allowed.
-
-    For UNIX sockets, supply the filename as a string.
-  wsgi_app: the WSGI 'application callable'; multiple WSGI applications
-    may be passed as (path_prefix, app) pairs.
-  numthreads: the number of worker threads to create (default 10).
-  server_name: the string to set for WSGI's SERVER_NAME environ entry.
-    Defaults to socket.gethostname().
-  max: the maximum number of queued requests (defaults to -1 = no limit).
-  request_queue_size: the 'backlog' argument to socket.listen();
-    specifies the maximum number of queued connections (default 5).
-  timeout: the timeout in seconds for accepted connections (default 10).
-
-  nodelay: if True (the default since 3.1), sets the TCP_NODELAY socket
-    option.
-
-  protocol: the version string to write in the Status-Line of all
-    HTTP responses. For example, "HTTP/1.1" (the default). This
-    also limits the supported features used in the response.
-
-
-  SSL/HTTPS
-  ---------
-  The OpenSSL module must be importable for SSL functionality.
-  You can obtain it from http://pyopenssl.sourceforge.net/
-
-  ssl_certificate: the filename of the server SSL certificate.
-  ssl_privatekey: the filename of the server's private key file.
-
-  If either of these is None (both are None by default), this server
-  will not use SSL. If both are given and are valid, they will be read
-  on server start and used in the SSL context for the listening socket.
-  """
-
-  protocol = "HTTP/1.1"
-  _bind_addr = "127.0.0.1"
-  version = "CherryPy/3.1.2"
-  ready = False
-  _interrupt = None
-
-  nodelay = True
-
-  ConnectionClass = HTTPConnection
-  environ = {
-    "SERVER_SOFTWARE": os.getenv('SERVER_SOFTWARE')
-  }
-
-  # Paths to certificate and private key files
-  ssl_certificate = None
-  ssl_private_key = None
-  ssl_certificate_chain = None
-  ssl_cipher_list = "DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2"
-  ssl_password_cb = None
-  ssl_no_renegotiation = False
-
-  def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
-         max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
-    self.requests = ThreadPool(self, min=numthreads or 1, max=max)
-
-    if callable(wsgi_app):
-      # We've been handed a single wsgi_app, in CP-2.1 style.
-      # Assume it's mounted at "".
-      self.wsgi_app = wsgi_app
-    else:
-      # We've been handed a list of (path_prefix, wsgi_app) tuples,
-      # so that the server can call different wsgi_apps, and also
-      # correctly set SCRIPT_NAME.
-      warnings.warn("The ability to pass multiple apps is deprecated "
-              "and will be removed in 3.2. You should explicitly "
-              "include a WSGIPathInfoDispatcher instead.",
-              DeprecationWarning)
-      self.wsgi_app = WSGIPathInfoDispatcher(wsgi_app)
-
-    self.bind_addr = bind_addr
-    if not server_name:
-      server_name = socket.gethostname()
-    self.server_name = server_name
-    self.request_queue_size = request_queue_size
-
-    self.timeout = timeout
-    self.shutdown_timeout = shutdown_timeout
-
-  def _get_numthreads(self):
-    return self.requests.min
-  def _set_numthreads(self, value):
-    self.requests.min = value
-  numthreads = property(_get_numthreads, _set_numthreads)
-
-  def __str__(self):
-    return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,
-                self.bind_addr)
-
-  def _get_bind_addr(self):
-    return self._bind_addr
-  def _set_bind_addr(self, value):
-    if isinstance(value, tuple) and value[0] in ('', None):
-      # Despite the socket module docs, using '' does not
-      # allow AI_PASSIVE to work. Passing None instead
-      # returns '0.0.0.0' like we want. In other words:
-      #     host    AI_PASSIVE     result
-      #      ''         Y         192.168.x.y
-      #      ''         N         192.168.x.y
-      #     None        Y         0.0.0.0
-      #     None        N         127.0.0.1
-      # But since you can get the same effect with an explicit
-      # '0.0.0.0', we deny both the empty string and None as values.
-      raise ValueError("Host values of '' or None are not allowed. "
-               "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead "
-               "to listen on all active interfaces.")
-    self._bind_addr = value
-  bind_addr = property(_get_bind_addr, _set_bind_addr,
-    doc="""The interface on which to listen for connections.
-
-    For TCP sockets, a (host, port) tuple. Host values may be any IPv4
-    or IPv6 address, or any valid hostname. The string 'localhost' is a
-    synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
-    The string '0.0.0.0' is a special IPv4 entry meaning "any active
-    interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
-    IPv6. The empty string or None are not allowed.
-
-    For UNIX sockets, supply the filename as a string.""")
-
-  def start(self):
-    """Run the server forever."""
-    self.bind_server()
-    self.listen_and_loop()
-
-
-  def bind_server(self):
-    # We don't have to trap KeyboardInterrupt or SystemExit here,
-    # because cherrpy.server already does so, calling self.stop() for us.
-    # If you're using this server with another framework, you should
-    # trap those exceptions in whatever code block calls start().
-    self._interrupt = None
-
-    # Select the appropriate socket
-    if isinstance(self.bind_addr, basestring):
-      # AF_UNIX socket
-
-      # So we can reuse the socket...
-      try:
-        os.unlink(self.bind_addr)
-      except IOError:
-        pass
-
-      # So everyone can access the socket...
-      try:
-        os.chmod(self.bind_addr, 0o777)
-      except IOError:
-        pass
-
-      info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
-    else:
-      # AF_INET or AF_INET6 socket
-      # Get the correct address family for our host (allows IPv6 addresses)
-      host, port = self.bind_addr
-      try:
-        info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
-                      socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
-      except socket.gaierror:
-        # Probably a DNS issue. Assume IPv4.
-        info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)]
-
-    self.socket = None
-    msg = "No socket could be created"
-    for res in info:
-      af, socktype, proto, canonname, sa = res
-      try:
-        self._bind(af, socktype, proto)
-      except socket.error as msg:
-        if self.socket:
-          self.socket.close()
-        self.socket = None
-        continue
-      break
-    if not self.socket:
-      raise socket.error(msg)
-
-  def listen_and_loop(self):
-    """
-    Listen on the socket, and then loop forever accepting and handling
-    connections.
+    """An HTTP server for WSGI.
+
+    bind_addr: The interface on which to listen for connections.
+        For TCP sockets, a (host, port) tuple. Host values may be any IPv4
+        or IPv6 address, or any valid hostname. The string 'localhost' is a
+        synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
+        The string '0.0.0.0' is a special IPv4 entry meaning "any active
+        interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
+        IPv6. The empty string or None are not allowed.
+
+        For UNIX sockets, supply the filename as a string.
+    wsgi_app: the WSGI 'application callable'; multiple WSGI applications
+        may be passed as (path_prefix, app) pairs.
+    numthreads: the number of worker threads to create (default 10).
+    server_name: the string to set for WSGI's SERVER_NAME environ entry.
+        Defaults to socket.gethostname().
+    max: the maximum number of queued requests (defaults to -1 = no limit).
+    request_queue_size: the 'backlog' argument to socket.listen();
+        specifies the maximum number of queued connections (default 5).
+    timeout: the timeout in seconds for accepted connections (default 10).
+
+    nodelay: if True (the default since 3.1), sets the TCP_NODELAY socket
+        option.
+
+    protocol: the version string to write in the Status-Line of all
+        HTTP responses. For example, "HTTP/1.1" (the default). This
+        also limits the supported features used in the response.
+
+
+    SSL/HTTPS
+    ---------
+    The OpenSSL module must be importable for SSL functionality.
+    You can obtain it from http://pyopenssl.sourceforge.net/
+
+    ssl_certificate: the filename of the server SSL certificate.
+    ssl_privatekey: the filename of the server's private key file.
+
+    If either of these is None (both are None by default), this server
+    will not use SSL. If both are given and are valid, they will be read
+    on server start and used in the SSL context for the listening socket.
     """
-    # Timeout so KeyboardInterrupt can be caught on Win32
-    self.socket.settimeout(1)
-    self.socket.listen(self.request_queue_size)
-
-    # Create worker threads
-    self.requests.start()
-
-    self.ready = True
-    while self.ready:
-      self.tick()
-      if self.interrupt:
-        while self.interrupt is True:
-          # Wait for self.stop() to complete. See _set_interrupt.
-          time.sleep(0.1)
-        if self.interrupt:
-          raise self.interrupt
-
-  def _bind(self, family, type, proto=0):
-    """Create (or recreate) the actual socket object."""
-    self.socket = socket.socket(family, type, proto)
-    prevent_socket_inheritance(self.socket)
-    self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-    if self.nodelay:
-      self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
-    if self.ssl_certificate and self.ssl_private_key:
-      if SSL is None:
-        raise ImportError("You must install pyOpenSSL to use HTTPS.")
-
-      # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473
-      ctx = SSL.Context(SSL.SSLv23_METHOD)
-
-      if self.ssl_password_cb is not None:
-        ctx.set_passwd_cb(self.ssl_password_cb)
-
-      ctx.set_cipher_list(self.ssl_cipher_list)
-      try:
-        ctx.use_privatekey_file(self.ssl_private_key)
-        ctx.use_certificate_file(self.ssl_certificate)
-        if self.ssl_certificate_chain:
-          ctx.use_certificate_chain_file(self.ssl_certificate_chain)
-      except Exception as ex:
-        logging.exception('SSL key and certificate could not be found or have a problem')
-        raise ex
-      options = SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3
-      if self.ssl_no_renegotiation:
-        options |= SSL.OP_NO_RENEGOTIATION
-      ctx.set_options(options)
-      self.socket = SSLConnection(ctx, self.socket)
-      self.populate_ssl_environ()
-
-      # If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
-      # activate dual-stack. See http://www.cherrypy.org/ticket/871.
-      if (not isinstance(self.bind_addr, basestring)
-        and self.bind_addr[0] == '::' and family == socket.AF_INET6):
-        try:
-          self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
-        except (AttributeError, socket.error):
-          # Apparently, the socket option is not available in
-          # this machine's TCP stack
-          pass
 
-    self.socket.bind(self.bind_addr)
+    protocol = "HTTP/1.1"
+    _bind_addr = "127.0.0.1"
+    version = "CherryPy/3.1.2"
+    ready = False
+    _interrupt = None
+
+    nodelay = True
+
+    ConnectionClass = HTTPConnection
+    environ = {
+        "SERVER_SOFTWARE": os.getenv('SERVER_SOFTWARE')
+    }
+
+    # Paths to certificate and private key files
+    ssl_certificate = None
+    ssl_private_key = None
+    ssl_certificate_chain = None
+    ssl_cipher_list = "DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2"
+    ssl_password_cb = None
+    ssl_no_renegotiation = False
+
+    def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
+                 max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
+        self.requests = ThreadPool(self, min=numthreads or 1, max=max)
+
+        if callable(wsgi_app):
+            # We've been handed a single wsgi_app, in CP-2.1 style.
+            # Assume it's mounted at "".
+            self.wsgi_app = wsgi_app
+        else:
+            # We've been handed a list of (path_prefix, wsgi_app) tuples,
+            # so that the server can call different wsgi_apps, and also
+            # correctly set SCRIPT_NAME.
+            warnings.warn("The ability to pass multiple apps is deprecated "
+                          "and will be removed in 3.2. You should explicitly "
+                          "include a WSGIPathInfoDispatcher instead.",
+                          DeprecationWarning)
+            self.wsgi_app = WSGIPathInfoDispatcher(wsgi_app)
+
+        self.bind_addr = bind_addr
+        if not server_name:
+            server_name = socket.gethostname()
+        self.server_name = server_name
+        self.request_queue_size = request_queue_size
+
+        self.timeout = timeout
+        self.shutdown_timeout = shutdown_timeout
+
+    def _get_numthreads(self):
+        return self.requests.min
+    def _set_numthreads(self, value):
+        self.requests.min = value
+    numthreads = property(_get_numthreads, _set_numthreads)
+
+    def __str__(self):
+        return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,
+                              self.bind_addr)
+
+    def _get_bind_addr(self):
+        return self._bind_addr
+    def _set_bind_addr(self, value):
+        if isinstance(value, tuple) and value[0] in ('', None):
+            # Despite the socket module docs, using '' does not
+            # allow AI_PASSIVE to work. Passing None instead
+            # returns '0.0.0.0' like we want. In other words:
+            #     host    AI_PASSIVE     result
+            #      ''         Y         192.168.x.y
+            #      ''         N         192.168.x.y
+            #     None        Y         0.0.0.0
+            #     None        N         127.0.0.1
+            # But since you can get the same effect with an explicit
+            # '0.0.0.0', we deny both the empty string and None as values.
+            raise ValueError("Host values of '' or None are not allowed. "
+                             "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead "
+                             "to listen on all active interfaces.")
+        self._bind_addr = value
+    bind_addr = property(_get_bind_addr, _set_bind_addr,
+        doc="""The interface on which to listen for connections.
+
+        For TCP sockets, a (host, port) tuple. Host values may be any IPv4
+        or IPv6 address, or any valid hostname. The string 'localhost' is a
+        synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
+        The string '0.0.0.0' is a special IPv4 entry meaning "any active
+        interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
+        IPv6. The empty string or None are not allowed.
+
+        For UNIX sockets, supply the filename as a string.""")
+
+    def start(self):
+        """Run the server forever."""
+        self.bind_server()
+        self.listen_and_loop()
+
+
+    def bind_server(self):
+        # We don't have to trap KeyboardInterrupt or SystemExit here,
+        # because cherrpy.server already does so, calling self.stop() for us.
+        # If you're using this server with another framework, you should
+        # trap those exceptions in whatever code block calls start().
+        self._interrupt = None
+
+        # Select the appropriate socket
+        if isinstance(self.bind_addr, basestring):
+            # AF_UNIX socket
+
+            # So we can reuse the socket...
+            try:
+              os.unlink(self.bind_addr)
+            except IOError:
+              pass
 
-  def tick(self):
-    """Accept a new connection and put it on the Queue."""
-    try:
-      s, addr = self.socket.accept()
-      prevent_socket_inheritance(s)
-      if not self.ready:
-        return
-      if hasattr(s, 'settimeout'):
-        s.settimeout(self.timeout)
-
-      environ = self.environ.copy()
-      # SERVER_SOFTWARE is common for IIS. It's also helpful for
-      # us to pass a default value for the "Server" response header.
-      if environ.get("SERVER_SOFTWARE") is None:
-        environ["SERVER_SOFTWARE"] = "%s WSGI Server" % self.version
-      # set a non-standard environ entry so the WSGI app can know what
-      # the *real* server protocol is (and what features to support).
-      # See http://www.faqs.org/rfcs/rfc2145.html.
-      environ["ACTUAL_SERVER_PROTOCOL"] = self.protocol
-      environ["SERVER_NAME"] = self.server_name
-
-      if isinstance(self.bind_addr, basestring):
-        # AF_UNIX. This isn't really allowed by WSGI, which doesn't
-        # address unix domain sockets. But it's better than nothing.
-        environ["SERVER_PORT"] = ""
-      else:
-        environ["SERVER_PORT"] = str(self.bind_addr[1])
-        # optional values
-        # Until we do DNS lookups, omit REMOTE_HOST
-        environ["REMOTE_ADDR"] = addr[0]
-        environ["REMOTE_PORT"] = str(addr[1])
-
-      conn = self.ConnectionClass(s, self.wsgi_app, environ)
-      self.requests.put(conn)
-    except socket.timeout:
-      # The only reason for the timeout in start() is so we can
-      # notice keyboard interrupts on Win32, which don't interrupt
-      # accept() by default
-      return
-    except socket.error as x:
-      if x.args[0] in socket_error_eintr:
-        # I *think* this is right. EINTR should occur when a signal
-        # is received during the accept() call; all docs say retry
-        # the call, and I *think* I'm reading it right that Python
-        # will then go ahead and poll for and handle the signal
-        # elsewhere. See http://www.cherrypy.org/ticket/707.
-        return
-      if x.args[0] in socket_errors_nonblocking:
-        # Just try again. See http://www.cherrypy.org/ticket/479.
-        return
-      if x.args[0] in socket_errors_to_ignore:
-        # Our socket was closed.
-        # See http://www.cherrypy.org/ticket/686.
-        return
-      raise
-
-  def _get_interrupt(self):
-    return self._interrupt
-  def _set_interrupt(self, interrupt):
-    self._interrupt = True
-    self.stop()
-    self._interrupt = interrupt
-  interrupt = property(_get_interrupt, _set_interrupt,
-             doc="Set this to an Exception instance to "
-               "interrupt the server.")
-
-  def stop(self):
-    """Gracefully shutdown a server that is serving forever."""
-    self.ready = False
-
-    sock = getattr(self, "socket", None)
-    if sock:
-      if not isinstance(self.bind_addr, basestring):
-        # Touch our own socket to make accept() return immediately.
-        try:
-          host, port = sock.getsockname()[:2]
-        except socket.error as x:
-          if x.args[0] not in socket_errors_to_ignore:
-            raise
+            # So everyone can access the socket...
+            try:
+              os.chmod(self.bind_addr, 0o777)
+            except IOError:
+              pass
+
+            info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
         else:
-          # Note that we're explicitly NOT using AI_PASSIVE,
-          # here, because we want an actual IP to touch.
-          # localhost won't work if we've bound to a public IP,
-          # but it will if we bound to '0.0.0.0' (INADDR_ANY).
-          for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
-                          socket.SOCK_STREAM):
+            # AF_INET or AF_INET6 socket
+            # Get the correct address family for our host (allows IPv6 addresses)
+            host, port = self.bind_addr
+            try:
+                info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
+                                          socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
+            except socket.gaierror:
+                # Probably a DNS issue. Assume IPv4.
+                info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)]
+
+        self.socket = None
+        msg = "No socket could be created"
+        for res in info:
             af, socktype, proto, canonname, sa = res
-            s = None
             try:
-              s = socket.socket(af, socktype, proto)
-              # See http://groups.google.com/group/cherrypy-users/
-              #        browse_frm/thread/bbfe5eb39c904fe0
-              s.settimeout(1.0)
-              s.connect((host, port))
-              s.close()
-            except socket.error:
-              if s:
-                s.close()
-      if hasattr(sock, "close"):
-        sock.close()
-      self.socket = None
-
-    self.requests.stop(self.shutdown_timeout)
-
-  def populate_ssl_environ(self):
-    """Create WSGI environ entries to be merged into each request."""
-    cert = open(self.ssl_certificate, 'rb').read()
-    cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
-    ssl_environ = {
-      "wsgi.url_scheme": "https",
-      "HTTPS": "on",
-      # pyOpenSSL doesn't provide access to any of these AFAICT
+                self._bind(af, socktype, proto)
+            except socket.error as msg:
+                if self.socket:
+                    self.socket.close()
+                self.socket = None
+                continue
+            break
+        if not self.socket:
+            raise socket.error(msg)
+
+    def listen_and_loop(self):
+        """
+        Listen on the socket, and then loop forever accepting and handling
+        connections.
+        """
+        # Timeout so KeyboardInterrupt can be caught on Win32
+        self.socket.settimeout(1)
+        self.socket.listen(self.request_queue_size)
+
+        # Create worker threads
+        self.requests.start()
+
+        self.ready = True
+        while self.ready:
+            self.tick()
+            if self.interrupt:
+                while self.interrupt is True:
+                    # Wait for self.stop() to complete. See _set_interrupt.
+                    time.sleep(0.1)
+                if self.interrupt:
+                    raise self.interrupt
+
+    def _bind(self, family, type, proto=0):
+        """Create (or recreate) the actual socket object."""
+        self.socket = socket.socket(family, type, proto)
+        prevent_socket_inheritance(self.socket)
+        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        if self.nodelay:
+            self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+        if self.ssl_certificate and self.ssl_private_key:
+            if SSL is None:
+                raise ImportError("You must install pyOpenSSL to use HTTPS.")
+
+            # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473
+            ctx = SSL.Context(SSL.SSLv23_METHOD)
+
+            if self.ssl_password_cb is not None:
+              ctx.set_passwd_cb(self.ssl_password_cb)
+
+            ctx.set_cipher_list(self.ssl_cipher_list)
+            try:
+              ctx.use_privatekey_file(self.ssl_private_key)
+              ctx.use_certificate_file(self.ssl_certificate)
+              if self.ssl_certificate_chain:
+                ctx.use_certificate_chain_file(self.ssl_certificate_chain)
+            except Exception as ex:
+              logging.exception('SSL key and certificate could not be found or have a problem')
+              raise ex
+            options = SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3
+            if self.ssl_no_renegotiation:
+              options |= SSL.OP_NO_RENEGOTIATION
+            ctx.set_options(options)
+            self.socket = SSLConnection(ctx, self.socket)
+            self.populate_ssl_environ()
+
+            # If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
+            # activate dual-stack. See http://www.cherrypy.org/ticket/871.
+            if (not isinstance(self.bind_addr, basestring)
+                and self.bind_addr[0] == '::' and family == socket.AF_INET6):
+                try:
+                    self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
+                except (AttributeError, socket.error):
+                    # Apparently, the socket option is not available in
+                    # this machine's TCP stack
+                    pass
+
+        self.socket.bind(self.bind_addr)
+
+    def tick(self):
+        """Accept a new connection and put it on the Queue."""
+        try:
+            s, addr = self.socket.accept()
+            prevent_socket_inheritance(s)
+            if not self.ready:
+                return
+            if hasattr(s, 'settimeout'):
+                s.settimeout(self.timeout)
+
+            environ = self.environ.copy()
+            # SERVER_SOFTWARE is common for IIS. It's also helpful for
+            # us to pass a default value for the "Server" response header.
+            if environ.get("SERVER_SOFTWARE") is None:
+                environ["SERVER_SOFTWARE"] = "%s WSGI Server" % self.version
+            # set a non-standard environ entry so the WSGI app can know what
+            # the *real* server protocol is (and what features to support).
+            # See http://www.faqs.org/rfcs/rfc2145.html.
+            environ["ACTUAL_SERVER_PROTOCOL"] = self.protocol
+            environ["SERVER_NAME"] = self.server_name
+
+            if isinstance(self.bind_addr, basestring):
+                # AF_UNIX. This isn't really allowed by WSGI, which doesn't
+                # address unix domain sockets. But it's better than nothing.
+                environ["SERVER_PORT"] = ""
+            else:
+                environ["SERVER_PORT"] = str(self.bind_addr[1])
+                # optional values
+                # Until we do DNS lookups, omit REMOTE_HOST
+                environ["REMOTE_ADDR"] = addr[0]
+                environ["REMOTE_PORT"] = str(addr[1])
+
+            conn = self.ConnectionClass(s, self.wsgi_app, environ)
+            self.requests.put(conn)
+        except socket.timeout:
+            # The only reason for the timeout in start() is so we can
+            # notice keyboard interrupts on Win32, which don't interrupt
+            # accept() by default
+            return
+        except socket.error as x:
+            if x.args[0] in socket_error_eintr:
+                # I *think* this is right. EINTR should occur when a signal
+                # is received during the accept() call; all docs say retry
+                # the call, and I *think* I'm reading it right that Python
+                # will then go ahead and poll for and handle the signal
+                # elsewhere. See http://www.cherrypy.org/ticket/707.
+                return
+            if x.args[0] in socket_errors_nonblocking:
+                # Just try again. See http://www.cherrypy.org/ticket/479.
+                return
+            if x.args[0] in socket_errors_to_ignore:
+                # Our socket was closed.
+                # See http://www.cherrypy.org/ticket/686.
+                return
+            raise
+
+    def _get_interrupt(self):
+        return self._interrupt
+    def _set_interrupt(self, interrupt):
+        self._interrupt = True
+        self.stop()
+        self._interrupt = interrupt
+    interrupt = property(_get_interrupt, _set_interrupt,
+                         doc="Set this to an Exception instance to "
+                             "interrupt the server.")
+
+    def stop(self):
+        """Gracefully shutdown a server that is serving forever."""
+        self.ready = False
+
+        sock = getattr(self, "socket", None)
+        if sock:
+            if not isinstance(self.bind_addr, basestring):
+                # Touch our own socket to make accept() return immediately.
+                try:
+                    host, port = sock.getsockname()[:2]
+                except socket.error as x:
+                    if x.args[0] not in socket_errors_to_ignore:
+                        raise
+                else:
+                    # Note that we're explicitly NOT using AI_PASSIVE,
+                    # here, because we want an actual IP to touch.
+                    # localhost won't work if we've bound to a public IP,
+                    # but it will if we bound to '0.0.0.0' (INADDR_ANY).
+                    for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
+                                                  socket.SOCK_STREAM):
+                        af, socktype, proto, canonname, sa = res
+                        s = None
+                        try:
+                            s = socket.socket(af, socktype, proto)
+                            # See http://groups.google.com/group/cherrypy-users/
+                            #        browse_frm/thread/bbfe5eb39c904fe0
+                            s.settimeout(1.0)
+                            s.connect((host, port))
+                            s.close()
+                        except socket.error:
+                            if s:
+                                s.close()
+            if hasattr(sock, "close"):
+                sock.close()
+            self.socket = None
+
+        self.requests.stop(self.shutdown_timeout)
+
+    def populate_ssl_environ(self):
+        """Create WSGI environ entries to be merged into each request."""
+        cert = open(self.ssl_certificate, 'rb').read()
+        cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
+        ssl_environ = {
+            "wsgi.url_scheme": "https",
+            "HTTPS": "on",
+            # pyOpenSSL doesn't provide access to any of these AFAICT
 ##            'SSL_PROTOCOL': 'SSLv2',
-##            SSL_CIPHER  string  The cipher specification name
-##            SSL_VERSION_INTERFACE   string  The mod_ssl program version
-##            SSL_VERSION_LIBRARY   string  The OpenSSL program version
-      }
-
-    # Server certificate attributes
-    ssl_environ.update({
-      'SSL_SERVER_M_VERSION': cert.get_version(),
-      'SSL_SERVER_M_SERIAL': cert.get_serial_number(),
+##            SSL_CIPHER 	string 	The cipher specification name
+##            SSL_VERSION_INTERFACE 	string 	The mod_ssl program version
+##            SSL_VERSION_LIBRARY 	string 	The OpenSSL program version
+            }
+
+        # Server certificate attributes
+        ssl_environ.update({
+            'SSL_SERVER_M_VERSION': cert.get_version(),
+            'SSL_SERVER_M_SERIAL': cert.get_serial_number(),
 ##            'SSL_SERVER_V_START': Validity of server's certificate (start time),
 ##            'SSL_SERVER_V_END': Validity of server's certificate (end time),
-      })
-
-    for prefix, dn in [("I", cert.get_issuer()),
-               ("S", cert.get_subject())]:
-      # X509Name objects don't seem to have a way to get the
-      # complete DN string. Use str() and slice it instead,
-      # because str(dn) == "<X509Name object '/C=US/ST=...'>"
-      dnstr = str(dn)[18:-2]
-
-      wsgikey = 'SSL_SERVER_%s_DN' % prefix
-      ssl_environ[wsgikey] = dnstr
-
-      # The DN should be of the form: /k1=v1/k2=v2, but we must allow
-      # for any value to contain slashes itself (in a URL).
-      while dnstr:
-        pos = dnstr.rfind("=")
-        dnstr, value = dnstr[:pos], dnstr[pos + 1:]
-        pos = dnstr.rfind("/")
-        dnstr, key = dnstr[:pos], dnstr[pos + 1:]
-        if key and value:
-          wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
-          ssl_environ[wsgikey] = value
-
-    self.environ.update(ssl_environ)
+            })
+
+        for prefix, dn in [("I", cert.get_issuer()),
+                           ("S", cert.get_subject())]:
+            # X509Name objects don't seem to have a way to get the
+            # complete DN string. Use str() and slice it instead,
+            # because str(dn) == "<X509Name object '/C=US/ST=...'>"
+            dnstr = str(dn)[18:-2]
+
+            wsgikey = 'SSL_SERVER_%s_DN' % prefix
+            ssl_environ[wsgikey] = dnstr
+
+            # The DN should be of the form: /k1=v1/k2=v2, but we must allow
+            # for any value to contain slashes itself (in a URL).
+            while dnstr:
+                pos = dnstr.rfind("=")
+                dnstr, value = dnstr[:pos], dnstr[pos + 1:]
+                pos = dnstr.rfind("/")
+                dnstr, key = dnstr[:pos], dnstr[pos + 1:]
+                if key and value:
+                    wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
+                    ssl_environ[wsgikey] = value
+
+        self.environ.update(ssl_environ)