0001"""A high-speed, production ready, thread pooled, generic WSGI server.
0002
0003Simplest example on how to use this module directly
0004(without using CherryPy's application machinery):
0005
0006    from cherrypy import wsgiserver
0007    
0008    def my_crazy_app(environ, start_response):
0009        status = '200 OK'
0010        response_headers = [('Content-type','text/plain')]
0011        start_response(status, response_headers)
0012        return ['Hello world!\n']
0013    
0014    # Here we set our application to the script_name '/' 
0015    wsgi_apps = [('/', my_crazy_app)]
0016    
0017    server = wsgiserver.CherryPyWSGIServer(('localhost', 8070), wsgi_apps,
0018                                           server_name='localhost')
0019    
0020    # Want SSL support? Just set these attributes
0021    # server.ssl_certificate = <filename>
0022    # server.ssl_private_key = <filename>
0023    
0024    if __name__ == '__main__':
0025        try:
0026            server.start()
0027        except KeyboardInterrupt:
0028            server.stop()
0029
0030This won't call the CherryPy engine (application side) at all, only the
0031WSGI server, which is independant from the rest of CherryPy. Don't
0032let the name "CherryPyWSGIServer" throw you; the name merely reflects
0033its origin, not it's coupling.
0034
0035The CherryPy WSGI server can serve as many WSGI applications
0036as you want in one instance:
0037
0038    wsgi_apps = [('/', my_crazy_app), ('/blog', my_blog_app)]
0039
0040"""
0041
0042
0043import base64
0044import Queue
0045import os
0046import re
0047quoted_slash = re.compile("(?i)%2F")
0048import rfc822
0049import socket
0050try:
0051    import cStringIO as StringIO
0052except ImportError:
0053    import StringIO
0054import sys
0055import threading
0056import time
0057import traceback
0058from urllib import unquote
0059from urlparse import urlparse
0060
0061try:
0062    from OpenSSL import SSL
0063    from OpenSSL import crypto
0064except ImportError:
0065    SSL = None
0066
0067import errno
0068socket_errors_to_ignore = []
0069# Not all of these names will be defined for every platform.
0070for _ in ("EPIPE", "ETIMEDOUT", "ECONNREFUSED", "ECONNRESET",
0071          "EHOSTDOWN", "EHOSTUNREACH",
0072          "WSAECONNABORTED", "WSAECONNREFUSED", "WSAECONNRESET",
0073          "WSAENETRESET", "WSAETIMEDOUT"):
0074    if _ in dir(errno):
0075        socket_errors_to_ignore.append(getattr(errno, _))
0076# de-dupe the list
0077socket_errors_to_ignore = dict.fromkeys(socket_errors_to_ignore).keys()
0078socket_errors_to_ignore.append("timed out")
0079
0080comma_separated_headers = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING',
0081    'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL',
0082    'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT',
0083    'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE',
0084    'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING',
0085    'WWW-AUTHENTICATE']
0086
0087class HTTPRequest(object):
0088    """An HTTP Request (and response).
0089    
0090    A single HTTP connection may consist of multiple request/response pairs.
0091    
0092    connection: the HTTP Connection object which spawned this request.
0093    rfile: the 'read' fileobject from the connection's socket
0094    ready: when True, the request has been parsed and is ready to begin
0095        generating the response. When False, signals the calling Connection
0096        that the response should not be generated and the connection should
0097        close.
0098    close_connection: signals the calling Connection that the request
0099        should close. This does not imply an error! The client and/or
0100        server may each request that the connection be closed.
0101    chunked_write: if True, output will be encoded with the "chunked"
0102        transfer-coding. This value is set automatically inside
0103        send_headers.
0104    """
0105
0106    def __init__(self, connection):
0107        self.connection = connection
0108        self.rfile = self.connection.rfile
0109        self.sendall = self.connection.sendall
0110        self.environ = connection.environ.copy()
0111
0112        self.ready = False
0113        self.started_response = False
0114        self.status = ""
0115        self.outheaders = []
0116        self.sent_headers = False
0117        self.close_connection = False
0118        self.chunked_write = False
0119
0120    def parse_request(self):
0121        """Parse the next HTTP request start-line and message-headers."""
0122        # HTTP/1.1 connections are persistent by default. If a client
0123        # requests a page, then idles (leaves the connection open),
0124        # then rfile.readline() will raise socket.error("timed out").
0125        # Note that it does this based on the value given to settimeout(),
0126        # and doesn't need the client to request or acknowledge the close
0127        # (although your TCP stack might suffer for it: cf Apache's history
0128        # with FIN_WAIT_2).
0129        request_line = self.rfile.readline()
0130        if not request_line:
0131            # Force self.ready = False so the connection will close.
0132            self.ready = False
0133            return
0134
0135        if request_line == "\r\n":
0136            # RFC 2616 sec 4.1: "...if the server is reading the protocol
0137            # stream at the beginning of a message and receives a CRLF
0138            # first, it should ignore the CRLF."
0139            # But only ignore one leading line! else we enable a DoS.
0140            request_line = self.rfile.readline()
0141            if not request_line:
0142                self.ready = False
0143                return
0144
0145        server = self.connection.server
0146        environ = self.environ
0147        environ["SERVER_SOFTWARE"] = "%s WSGI Server" % server.version
0148
0149        method, path, req_protocol = request_line.strip().split(" ", 2)
0150        environ["REQUEST_METHOD"] = method
0151
0152        # path may be an abs_path (including "http://host.domain.tld");
0153        scheme, location, path, params, qs, frag = urlparse(path)
0154
0155        if frag:
0156            self.simple_response("400 Bad Request",
0157                                 "Illegal #fragment in Request-URI.")
0158            return
0159
0160        if scheme:
0161            environ["wsgi.url_scheme"] = scheme
0162        if params:
0163            path = path + ";" + params
0164
0165        # Unquote the path+params (e.g. "/this%20path" -> "this path").
0166        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
0167        #
0168        # But note that "...a URI must be separated into its components
0169        # before the escaped characters within those components can be
0170        # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
0171        atoms = [unquote(x) for x in quoted_slash.split(path)]
0172        path = "%2F".join(atoms)
0173
0174        if path == "*":
0175            # This means, of course, that the last wsgi_app (shortest path)
0176            # will always handle a URI of "*".
0177            environ["SCRIPT_NAME"] = ""
0178            environ["PATH_INFO"] = "*"
0179            self.wsgi_app = server.mount_points[-1][1]
0180        else:
0181            for mount_point, wsgi_app in server.mount_points:
0182                # The mount_points list should be sorted by length, descending.
0183                if path.startswith(mount_point + "/") or path == mount_point:
0184                    environ["SCRIPT_NAME"] = mount_point
0185                    environ["PATH_INFO"] = path[len(mount_point):]
0186                    self.wsgi_app = wsgi_app
0187                    break
0188            else:
0189                self.simple_response("404 Not Found")
0190                return
0191
0192        # Note that, like wsgiref and most other WSGI servers,
0193        # we unquote the path but not the query string.
0194        environ["QUERY_STRING"] = qs
0195
0196        # Compare request and server HTTP protocol versions, in case our
0197        # server does not support the requested protocol. Limit our output
0198        # to min(req, server). We want the following output:
0199        #     request    server     actual written   supported response
0200        #     protocol   protocol  response protocol    feature set
0201        # a     1.0        1.0           1.0                1.0
0202        # b     1.0        1.1           1.1                1.0
0203        # c     1.1        1.0           1.0                1.0
0204        # d     1.1        1.1           1.1                1.1
0205        # Notice that, in (b), the response will be "HTTP/1.1" even though
0206        # the client only understands 1.0. RFC 2616 10.5.6 says we should
0207        # only return 505 if the _major_ version is different.
0208        rp = int(req_protocol[5]), int(req_protocol[7])
0209        sp = int(server.protocol[5]), int(server.protocol[7])
0210        if sp[0] != rp[0]:
0211            self.simple_response("505 HTTP Version Not Supported")
0212            return
0213        # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol.
0214        environ["SERVER_PROTOCOL"] = req_protocol
0215        # set a non-standard environ entry so the WSGI app can know what
0216        # the *real* server protocol is (and what features to support).
0217        # See http://www.faqs.org/rfcs/rfc2145.html.
0218        environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol
0219        self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
0220
0221        # If the Request-URI was an absoluteURI, use its location atom.
0222        if location:
0223            environ["SERVER_NAME"] = location
0224
0225        # then all the http headers
0226        try:
0227            self.read_headers()
0228        except ValueError, ex:
0229            self.simple_response("400 Bad Request", repr(ex.args))
0230            return
0231
0232        creds = environ.get("HTTP_AUTHORIZATION", "").split(" ", 1)
0233        environ["AUTH_TYPE"] = creds[0]
0234        if creds[0].lower() == 'basic':
0235            user, pw = base64.decodestring(creds[1]).split(":", 1)
0236            environ["REMOTE_USER"] = user
0237
0238        # Persistent connection support
0239        if self.response_protocol == "HTTP/1.1":
0240            if environ.get("HTTP_CONNECTION", "") == "close":
0241                self.close_connection = True
0242        else:
0243            # HTTP/1.0
0244            if environ.get("HTTP_CONNECTION", "") != "Keep-Alive":
0245                self.close_connection = True
0246
0247        # Transfer-Encoding support
0248        te = None
0249        if self.response_protocol == "HTTP/1.1":
0250            te = environ.get("HTTP_TRANSFER_ENCODING")
0251            if te:
0252                te = [x.strip().lower() for x in te.split(",") if x.strip()]
0253
0254        read_chunked = False
0255
0256        if te:
0257            for enc in te:
0258                if enc == "chunked":
0259                    read_chunked = True
0260                else:
0261                    # Note that, even if we see "chunked", we must reject
0262                    # if there is an extension we don't recognize.
0263                    self.simple_response("501 Unimplemented")
0264                    self.close_connection = True
0265                    return
0266
0267        if read_chunked:
0268            if not self.decode_chunked():
0269                return
0270
0271        # From PEP 333:
0272        # "Servers and gateways that implement HTTP 1.1 must provide
0273        # transparent support for HTTP 1.1's "expect/continue" mechanism.
0274        # This may be done in any of several ways:
0275        #   1. Respond to requests containing an Expect: 100-continue request
0276        #      with an immediate "100 Continue" response, and proceed normally.
0277        #   2. Proceed with the request normally, but provide the application
0278        #      with a wsgi.input stream that will send the "100 Continue"
0279        #      response if/when the application first attempts to read from
0280        #      the input stream. The read request must then remain blocked
0281        #      until the client responds.
0282        #   3. Wait until the client decides that the server does not support
0283        #      expect/continue, and sends the request body on its own.
0284        #      (This is suboptimal, and is not recommended.)
0285        #
0286        # We used to do 3, but are now doing 1. Maybe we'll do 2 someday,
0287        # but it seems like it would be a big slowdown for such a rare case.
0288        if environ.get("HTTP_EXPECT", "") == "100-continue":
0289            self.simple_response(100)
0290
0291        self.ready = True
0292
0293    def read_headers(self):
0294        """Read header lines from the incoming stream."""
0295        environ = self.environ
0296
0297        while True:
0298            line = self.rfile.readline()
0299            if not line:
0300                # No more data--illegal end of headers
0301                raise ValueError("Illegal end of headers.")
0302
0303            if line == '\r\n':
0304                # Normal end of headers
0305                break
0306
0307            if line[0] in ' \t':
0308                # It's a continuation line.
0309                v = line.strip()
0310            else:
0311                k, v = line.split(":", 1)
0312                k, v = k.strip().upper(), v.strip()
0313                envname = "HTTP_" + k.replace("-", "_")
0314
0315            if k in comma_separated_headers:
0316                existing = environ.get(envname)
0317                if existing:
0318                    v = ", ".join((existing, v))
0319            environ[envname] = v
0320
0321        ct = environ.pop("HTTP_CONTENT_TYPE", None)
0322        if ct:
0323            environ["CONTENT_TYPE"] = ct
0324        cl = environ.pop("HTTP_CONTENT_LENGTH", None)
0325        if cl:
0326            environ["CONTENT_LENGTH"] = cl
0327
0328    def decode_chunked(self):
0329        """Decode the 'chunked' transfer coding."""
0330        cl = 0
0331        data = StringIO.StringIO()
0332        while True:
0333            line = self.rfile.readline().strip().split(";", 1)
0334            chunk_size = int(line.pop(0), 16)
0335            if chunk_size <= 0:
0336                break
0337##            if line: chunk_extension = line[0]
0338            cl += chunk_size
0339            data.write(self.rfile.read(chunk_size))
0340            crlf = self.rfile.read(2)
0341            if crlf != "\r\n":
0342                self.simple_response("400 Bad Request",
0343                                     "Bad chunked transfer coding "
0344                                     "(expected '\\r\\n', got %r)" % crlf)
0345                return
0346
0347        # Grab any trailer headers
0348        self.read_headers()
0349
0350        data.seek(0)
0351        self.environ["wsgi.input"] = data
0352        self.environ["CONTENT_LENGTH"] = str(cl) or ""
0353        return True
0354
0355    def respond(self):
0356        """Call the appropriate WSGI app and write its iterable output."""
0357        response = self.wsgi_app(self.environ, self.start_response)
0358        try:
0359            for chunk in response:
0360                # "The start_response callable must not actually transmit
0361                # the response headers. Instead, it must store them for the
0362                # server or gateway to transmit only after the first
0363                # iteration of the application return value that yields
0364                # a NON-EMPTY string, or upon the application's first
0365                # invocation of the write() callable." (PEP 333)
0366                if chunk:
0367                    self.write(chunk)
0368        finally:
0369            if hasattr(response, "close"):
0370                response.close()
0371        if (self.ready and not self.sent_headers
0372                and not self.connection.server.interrupt):
0373            self.sent_headers = True
0374            self.send_headers()
0375        if self.chunked_write:
0376            self.sendall("0\r\n\r\n")
0377
0378    def simple_response(self, status, msg=""):
0379        """Write a simple response back to the client."""
0380        status = str(status)
0381        buf = ["%s %s\r\n" % (self.connection.server.protocol, status),
0382               "Content-Length: %s\r\n" % len(msg)]
0383
0384        if status[:3] == "413" and self.response_protocol == 'HTTP/1.1':
0385            # Request Entity Too Large
0386            self.close_connection = True
0387            buf.append("Connection: close\r\n")
0388
0389        buf.append("\r\n")
0390        if msg:
0391            buf.append(msg)
0392        self.sendall("".join(buf))
0393
0394    def start_response(self, status, headers, exc_info = None):
0395        """WSGI callable to begin the HTTP response."""
0396        if self.started_response:
0397            if not exc_info:
0398                raise AssertionError("WSGI start_response called a second "
0399                                     "time with no exc_info.")
0400            else:
0401                try:
0402                    raise exc_info[0], exc_info[1], exc_info[2]
0403                finally:
0404                    exc_info = None
0405        self.started_response = True
0406        self.status = status
0407        self.outheaders.extend(headers)
0408        return self.write
0409
0410    def write(self, chunk):
0411        """WSGI callable to write unbuffered data to the client.
0412        
0413        This method is also used internally by start_response (to write
0414        data from the iterable returned by the WSGI application).
0415        """
0416        if not self.started_response:
0417            raise AssertionError("WSGI write called before start_response.")
0418
0419        if not self.sent_headers:
0420            self.sent_headers = True
0421            self.send_headers()
0422
0423        if self.chunked_write and chunk:
0424            buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"]
0425            self.sendall("".join(buf))
0426        else:
0427            self.sendall(chunk)
0428
0429    def send_headers(self):
0430        """Assert, process, and send the HTTP response message-headers."""
0431        hkeys = [key.lower() for key, value in self.outheaders]
0432        status = int(self.status[:3])
0433
0434        if status == 413:
0435            # Request Entity Too Large. Close conn to avoid garbage.
0436            self.close_connection = True
0437        elif "content-length" not in hkeys:
0438            # "All 1xx (informational), 204 (no content),
0439            # and 304 (not modified) responses MUST NOT
0440            # include a message-body." So no point chunking.
0441            if status < 200 or status in (204, 205, 304):
0442                pass
0443            else:
0444                if self.response_protocol == 'HTTP/1.1':
0445                    # Use the chunked transfer-coding
0446                    self.chunked_write = True
0447                    self.outheaders.append(("Transfer-Encoding", "chunked"))
0448                else:
0449                    # Closing the conn is the only way to determine len.
0450                    self.close_connection = True
0451
0452        if "connection" not in hkeys:
0453            if self.response_protocol == 'HTTP/1.1':
0454                if self.close_connection:
0455                    self.outheaders.append(("Connection", "close"))
0456            else:
0457                if not self.close_connection:
0458                    self.outheaders.append(("Connection", "Keep-Alive"))
0459
0460        if "date" not in hkeys:
0461            self.outheaders.append(("Date", rfc822.formatdate()))
0462
0463        server = self.connection.server
0464
0465        if "server" not in hkeys:
0466            self.outheaders.append(("Server", server.version))
0467
0468        buf = [server.protocol, " ", self.status, "\r\n"]
0469        try:
0470            buf += [k + ": " + v + "\r\n" for k, v in self.outheaders]
0471        except TypeError:
0472            if not isinstance(k, str):
0473                raise TypeError("WSGI response header key %r is not a string.")
0474            if not isinstance(v, str):
0475                raise TypeError("WSGI response header value %r is not a string.")
0476            else:
0477                raise
0478        buf.append("\r\n")
0479        self.sendall("".join(buf))
0480
0481
0482class NoSSLError(Exception):
0483    """Exception raised when a client speaks HTTP to an HTTPS socket."""
0484    pass
0485
0486
0487def _ssl_wrap_method(method, is_reader=False):
0488    """Wrap the given method with SSL error-trapping.
0489    
0490    is_reader: if False (the default), EOF errors will be raised.
0491        If True, EOF errors will return "" (to emulate normal sockets).
0492    """
0493    def ssl_method_wrapper(self, *args, **kwargs):
0494##        print (id(self), method, args, kwargs)
0495        start = time.time()
0496        while True:
0497            try:
0498                return method(self, *args, **kwargs)
0499            except (SSL.WantReadError, SSL.WantWriteError):
0500                # Sleep and try again. This is dangerous, because it means
0501                # the rest of the stack has no way of differentiating
0502                # between a "new handshake" error and "client dropped".
0503                # Note this isn't an endless loop: there's a timeout below.
0504                time.sleep(self.ssl_retry)
0505            except SSL.SysCallError, e:
0506                if is_reader and e.args == (-1, 'Unexpected EOF'):
0507                    return ""
0508
0509                errno = e.args[0]
0510                if is_reader and errno in socket_errors_to_ignore:
0511                    return ""
0512                raise socket.error(errno)
0513            except SSL.Error, e:
0514                if is_reader and e.args == (-1, 'Unexpected EOF'):
0515                    return ""
0516
0517                thirdarg = None
0518                try:
0519                    thirdarg = e.args[0][0][2]
0520                except IndexError:
0521                    pass
0522
0523                if is_reader and thirdarg == 'ssl handshake failure':
0524                    return ""
0525                if thirdarg == 'http request':
0526                    # The client is talking HTTP to an HTTPS server.
0527                    raise NoSSLError()
0528                raise
0529            if time.time() - start > self.ssl_timeout:
0530                raise socket.timeout("timed out")
0531    return ssl_method_wrapper
0532
0533class SSL_fileobject(socket._fileobject):
0534    """Faux file object attached to a socket object."""
0535
0536    ssl_timeout = 3
0537    ssl_retry = .01
0538
0539    close = _ssl_wrap_method(socket._fileobject.close)
0540    flush = _ssl_wrap_method(socket._fileobject.flush)
0541    write = _ssl_wrap_method(socket._fileobject.write)
0542    writelines = _ssl_wrap_method(socket._fileobject.writelines)
0543    read = _ssl_wrap_method(socket._fileobject.read, is_reader=True)
0544    readline = _ssl_wrap_method(socket._fileobject.readline, is_reader=True)
0545    readlines = _ssl_wrap_method(socket._fileobject.readlines, is_reader=True)
0546
0547
0548class HTTPConnection(object):
0549    """An HTTP connection (active socket).
0550    
0551    socket: the raw socket object (usually TCP) for this connection.
0552    addr: the "bind address" for the remote end of the socket.
0553        For IP sockets, this is a tuple of (REMOTE_ADDR, REMOTE_PORT).
0554        For UNIX domain sockets, this will be a string.
0555    server: the HTTP Server for this Connection. Usually, the server
0556        object possesses a passive (server) socket which spawns multiple,
0557        active (client) sockets, one for each connection.
0558    
0559    environ: a WSGI environ template. This will be copied for each request.
0560    rfile: a fileobject for reading from the socket.
0561    sendall: a function for writing (+ flush) to the socket.
0562    """
0563
0564    rbufsize = -1
0565    RequestHandlerClass = HTTPRequest
0566    environ = {"wsgi.version": (1, 0),
0567               "wsgi.url_scheme": "http",
0568               "wsgi.multithread": True,
0569               "wsgi.multiprocess": False,
0570               "wsgi.run_once": False,
0571               "wsgi.errors": sys.stderr,
0572               }
0573
0574    def __init__(self, sock, addr, server):
0575        self.socket = sock
0576        self.addr = addr
0577        self.server = server
0578
0579        # Copy the class environ into self.
0580        self.environ = self.environ.copy()
0581
0582        if SSL and isinstance(sock, SSL.ConnectionType):
0583            timeout = sock.gettimeout()
0584            self.rfile = SSL_fileobject(sock, "r", self.rbufsize)
0585            self.rfile.ssl_timeout = timeout
0586            self.sendall = _ssl_wrap_method(sock.sendall)
0587            self.environ["wsgi.url_scheme"] = "https"
0588            self.environ["HTTPS"] = "on"
0589            sslenv = getattr(server, "ssl_environ", None)
0590            if sslenv:
0591                self.environ.update(sslenv)
0592        else:
0593            self.rfile = sock.makefile("rb", self.rbufsize)
0594            self.sendall = sock.sendall
0595
0596        self.environ.update({"wsgi.input": self.rfile,
0597                             "SERVER_NAME": self.server.server_name,
0598                             })
0599
0600        if isinstance(self.server.bind_addr, basestring):
0601            # AF_UNIX. This isn't really allowed by WSGI, which doesn't
0602            # address unix domain sockets. But it's better than nothing.
0603            self.environ["SERVER_PORT"] = ""
0604        else:
0605            self.environ["SERVER_PORT"] = str(self.server.bind_addr[1])
0606            # optional values
0607            # Until we do DNS lookups, omit REMOTE_HOST
0608            self.environ["REMOTE_ADDR"] = self.addr[0]
0609            self.environ["REMOTE_PORT"] = str(self.addr[1])
0610
0611    def communicate(self):
0612        """Read each request and respond appropriately."""
0613        try:
0614            while True:
0615                # (re)set req to None so that if something goes wrong in
0616                # the RequestHandlerClass constructor, the error doesn't
0617                # get written to the previous request.
0618                req = None
0619                req = self.RequestHandlerClass(self)
0620                # This order of operations should guarantee correct pipelining.
0621                req.parse_request()
0622                if not req.ready:
0623                    return
0624                req.respond()
0625                if req.close_connection:
0626                    return
0627        except socket.error, e:
0628            errno = e.args[0]
0629            if errno not in socket_errors_to_ignore:
0630                if req:
0631                    req.simple_response("500 Internal Server Error",
0632                                        format_exc())
0633            return
0634        except (KeyboardInterrupt, SystemExit):
0635            raise
0636        except NoSSLError:
0637            # Unwrap our sendall
0638            req.sendall = self.socket._sock.sendall
0639            req.simple_response("400 Bad Request",
0640                                "The client sent a plain HTTP request, but "
0641                                "this server only speaks HTTPS on this port.")
0642        except:
0643            if req:
0644                req.simple_response("500 Internal Server Error", format_exc())
0645
0646    def close(self):
0647        """Close the socket underlying this connection."""
0648        self.rfile.close()
0649        self.socket.close()
0650
0651
0652def format_exc(limit=None):
0653    """Like print_exc() but return a string. Backport for Python 2.3."""
0654    try:
0655        etype, value, tb = sys.exc_info()
0656        return ''.join(traceback.format_exception(etype, value, tb, limit))
0657    finally:
0658        etype = value = tb = None
0659
0660
0661_SHUTDOWNREQUEST = None
0662
0663class WorkerThread(threading.Thread):
0664    """Thread which continuously polls a Queue for Connection objects.
0665    
0666    server: the HTTP Server which spawned this thread, and which owns the
0667        Queue and is placing active connections into it.
0668    ready: a simple flag for the calling server to know when this thread
0669        has begun polling the Queue.
0670    
0671    Due to the timing issues of polling a Queue, a WorkerThread does not
0672    check its own 'ready' flag after it has started. To stop the thread,
0673    it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
0674    (one for each running WorkerThread).
0675    """
0676
0677    def __init__(self, server):
0678        self.ready = False
0679        self.server = server
0680        threading.Thread.__init__(self)
0681
0682    def run(self):
0683        try:
0684            self.ready = True
0685            while True:
0686                conn = self.server.requests.get()
0687                if conn is _SHUTDOWNREQUEST:
0688                    return
0689
0690                try:
0691                    conn.communicate()
0692                finally:
0693                    conn.close()
0694        except (KeyboardInterrupt, SystemExit), exc:
0695            self.server.interrupt = exc
0696
0697
0698class SSLConnection:
0699    """A thread-safe wrapper for an SSL.Connection.
0700    
0701    *args: the arguments to create the wrapped SSL.Connection(*args).
0702    """
0703
0704    def __init__(self, *args):
0705        self._ssl_conn = SSL.Connection(*args)
0706        self._lock = threading.RLock()
0707
0708    for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
0709              'renegotiate', 'bind', 'listen', 'connect', 'accept',
0710              'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list',
0711              'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
0712              'makefile', 'get_app_data', 'set_app_data', 'state_string',
0713              'sock_shutdown', 'get_peer_certificate', 'want_read',
0714              'want_write', 'set_connect_state', 'set_accept_state',
0715              'connect_ex', 'sendall', 'settimeout'):
0716        exec """def %s(self, *args):
0717        self._lock.acquire()
0718        try:
0719            return self._ssl_conn.%s(*args)
0720        finally:
0721            self._lock.release()
0722""" % (f, f)
0723
0724
0725class CherryPyWSGIServer(object):
0726    """An HTTP server for WSGI.
0727    
0728    bind_addr: a (host, port) tuple if TCP sockets are desired;
0729        for UNIX sockets, supply the filename as a string.
0730    wsgi_app: the WSGI 'application callable'; multiple WSGI applications
0731        may be passed as (script_name, callable) pairs.
0732    numthreads: the number of worker threads to create (default 10).
0733    server_name: the string to set for WSGI's SERVER_NAME environ entry.
0734        Defaults to socket.gethostname().
0735    max: the maximum number of queued requests (defaults to -1 = no limit).
0736    request_queue_size: the 'backlog' argument to socket.listen();
0737        specifies the maximum number of queued connections (default 5).
0738    timeout: the timeout in seconds for accepted connections (default 10).
0739    
0740    protocol: the version string to write in the Status-Line of all
0741        HTTP responses. For example, "HTTP/1.1" (the default). This
0742        also limits the supported features used in the response.
0743    
0744    
0745    SSL/HTTPS
0746    ---------
0747    The OpenSSL module must be importable for SSL functionality.
0748    You can obtain it from http://pyopenssl.sourceforge.net/
0749    
0750    ssl_certificate: the filename of the server SSL certificate.
0751    ssl_privatekey: the filename of the server's private key file.
0752    
0753    If either of these is None (both are None by default), this server
0754    will not use SSL. If both are given and are valid, they will be read
0755    on server start and used in the SSL context for the listening socket.
0756    """
0757
0758    protocol = "HTTP/1.1"
0759    version = "CherryPy/3.0.2"
0760    ready = False
0761    _interrupt = None
0762    ConnectionClass = HTTPConnection
0763
0764    # Paths to certificate and private key files
0765    ssl_certificate = None
0766    ssl_private_key = None
0767
0768    def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
0769                 max=-1, request_queue_size=5, timeout=10):
0770        self.requests = Queue.Queue(max)
0771
0772        if callable(wsgi_app):
0773            # We've been handed a single wsgi_app, in CP-2.1 style.
0774            # Assume it's mounted at "".
0775            self.mount_points = [("", wsgi_app)]
0776        else:
0777            # We've been handed a list of (mount_point, wsgi_app) tuples,
0778            # so that the server can call different wsgi_apps, and also
0779            # correctly set SCRIPT_NAME.
0780            self.mount_points = wsgi_app
0781        self.mount_points.sort()
0782        self.mount_points.reverse()
0783
0784        self.bind_addr = bind_addr
0785        self.numthreads = numthreads or 1
0786        if not server_name:
0787            server_name = socket.gethostname()
0788        self.server_name = server_name
0789        self.request_queue_size = request_queue_size
0790        self._workerThreads = []
0791
0792        self.timeout = timeout
0793
0794    def start(self):
0795        """Run the server forever."""
0796        # We don't have to trap KeyboardInterrupt or SystemExit here,
0797        # because cherrpy.server already does so, calling self.stop() for us.
0798        # If you're using this server with another framework, you should
0799        # trap those exceptions in whatever code block calls start().
0800        self._interrupt = None
0801
0802        # Select the appropriate socket
0803        if isinstance(self.bind_addr, basestring):
0804            # AF_UNIX socket
0805
0806            # So we can reuse the socket...
0807            try: os.unlink(self.bind_addr)
0808            except: pass
0809
0810            # So everyone can access the socket...
0811            try: os.chmod(self.bind_addr, 0777)
0812            except: pass
0813
0814            info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
0815        else:
0816            # AF_INET or AF_INET6 socket
0817            # Get the correct address family for our host (allows IPv6 addresses)
0818            host, port = self.bind_addr
0819            flags = 0
0820            if host == '':
0821                # Despite the socket module docs, using '' does not
0822                # allow AI_PASSIVE to work. Passing None instead
0823                # returns '0.0.0.0' like we want. In other words:
0824                #     host    AI_PASSIVE     result
0825                #      ''         Y         192.168.x.y
0826                #      ''         N         192.168.x.y
0827                #     None        Y         0.0.0.0
0828                #     None        N         127.0.0.1
0829                host = None
0830                flags = socket.AI_PASSIVE
0831            try:
0832                info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
0833                                          socket.SOCK_STREAM, 0, flags)
0834            except socket.gaierror:
0835                # Probably a DNS issue. Assume IPv4.
0836                info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)]
0837
0838        self.socket = None
0839        msg = "No socket could be created"
0840        for res in info:
0841            af, socktype, proto, canonname, sa = res
0842            try:
0843                self.bind(af, socktype, proto)
0844            except socket.error, msg:
0845                if self.socket:
0846                    self.socket.close()
0847                self.socket = None
0848                continue
0849            break
0850        if not self.socket:
0851            raise socket.error, msg
0852
0853        # Timeout so KeyboardInterrupt can be caught on Win32
0854        self.socket.settimeout(1)
0855        self.socket.listen(self.request_queue_size)
0856
0857        # Create worker threads
0858        for i in xrange(self.numthreads):
0859            self._workerThreads.append(WorkerThread(self))
0860        for worker in self._workerThreads:
0861            worker.setName("CP WSGIServer " + worker.getName())
0862            worker.start()
0863        for worker in self._workerThreads:
0864            while not worker.ready:
0865                time.sleep(.1)
0866
0867        self.ready = True
0868        while self.ready:
0869            self.tick()
0870            if self.interrupt:
0871                while self.interrupt is True:
0872                    # Wait for self.stop() to complete. See _set_interrupt.
0873                    time.sleep(0.1)
0874                raise self.interrupt
0875
0876    def bind(self, family, type, proto=0):
0877        """Create (or recreate) the actual socket object."""
0878        self.socket = socket.socket(family, type, proto)
0879        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
0880##        self.socket.setsockopt(socket.SOL_SOCKET, socket.TCP_NODELAY, 1)
0881        if self.ssl_certificate and self.ssl_private_key:
0882            if SSL is None:
0883                raise ImportError("You must install pyOpenSSL to use HTTPS.")
0884
0885            # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473
0886            ctx = SSL.Context(SSL.SSLv23_METHOD)
0887            ctx.use_privatekey_file(self.ssl_private_key)
0888            ctx.use_certificate_file(self.ssl_certificate)
0889            self.socket = SSLConnection(ctx, self.socket)
0890            self.populate_ssl_environ()
0891        self.socket.bind(self.bind_addr)
0892
0893    def tick(self):
0894        """Accept a new connection and put it on the Queue."""
0895        try:
0896            s, addr = self.socket.accept()
0897            if not self.ready:
0898                return
0899            if hasattr(s, 'settimeout'):
0900                s.settimeout(self.timeout)
0901            conn = self.ConnectionClass(s, addr, self)
0902            self.requests.put(conn)
0903        except socket.timeout:
0904            # The only reason for the timeout in start() is so we can
0905            # notice keyboard interrupts on Win32, which don't interrupt
0906            # accept() by default
0907            return
0908        except socket.error, x:
0909            msg = x.args[1]
0910            if msg in ("Bad file descriptor", "Socket operation on non-socket"):
0911                # Our socket was closed.
0912                return
0913            if msg == "Resource temporarily unavailable":
0914                # Just try again. See http://www.cherrypy.org/ticket/479.
0915                return
0916            raise
0917
0918    def _get_interrupt(self):
0919        return self._interrupt
0920    def _set_interrupt(self, interrupt):
0921        self._interrupt = True
0922        self.stop()
0923        self._interrupt = interrupt
0924    interrupt = property(_get_interrupt, _set_interrupt,
0925                         doc="Set this to an Exception instance to "
0926                             "interrupt the server.")
0927
0928    def stop(self):
0929        """Gracefully shutdown a server that is serving forever."""
0930        self.ready = False
0931
0932        sock = getattr(self, "socket", None)
0933        if sock:
0934            if not isinstance(self.bind_addr, basestring):
0935                # Touch our own socket to make accept() return immediately.
0936                try:
0937                    host, port = sock.getsockname()[:2]
0938                except socket.error, x:
0939                    if x.args[1] != "Bad file descriptor":
0940                        raise
0941                else:
0942                    # Note that we're explicitly NOT using AI_PASSIVE,
0943                    # here, because we want an actual IP to touch.
0944                    # localhost won't work if we've bound to a public IP,
0945                    # but it would if we bound to INADDR_ANY via host = ''.
0946                    for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
0947                                                  socket.SOCK_STREAM):
0948                        af, socktype, proto, canonname, sa = res
0949                        s = None
0950                        try:
0951                            s = socket.socket(af, socktype, proto)
0952                            # See http://groups.google.com/group/cherrypy-users/
0953                            #        browse_frm/thread/bbfe5eb39c904fe0
0954                            s.settimeout(1.0)
0955                            s.connect((host, port))
0956                            s.close()
0957                        except socket.error:
0958                            if s:
0959                                s.close()
0960            if hasattr(sock, "close"):
0961                sock.close()
0962            self.socket = None
0963
0964        # Must shut down threads here so the code that calls
0965        # this method can know when all threads are stopped.
0966        for worker in self._workerThreads:
0967            self.requests.put(_SHUTDOWNREQUEST)
0968
0969        # Don't join currentThread (when stop is called inside a request).
0970        current = threading.currentThread()
0971        while self._workerThreads:
0972            worker = self._workerThreads.pop()
0973            if worker is not current and worker.isAlive:
0974                try:
0975                    worker.join()
0976                except AssertionError:
0977                    pass
0978
0979    def populate_ssl_environ(self):
0980        """Create WSGI environ entries to be merged into each request."""
0981        cert = open(self.ssl_certificate).read()
0982        cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
0983        self.ssl_environ = {
0984            # pyOpenSSL doesn't provide access to any of these AFAICT
0985##            'SSL_PROTOCOL': 'SSLv2',
0986##            SSL_CIPHER        string  The cipher specification name
0987##            SSL_VERSION_INTERFACE     string  The mod_ssl program version
0988##            SSL_VERSION_LIBRARY       string  The OpenSSL program version
0989            }
0990
0991        # Server certificate attributes
0992        self.ssl_environ.update({
0993            'SSL_SERVER_M_VERSION': cert.get_version(),
0994            'SSL_SERVER_M_SERIAL': cert.get_serial_number(),
0995##            'SSL_SERVER_V_START': Validity of server's certificate (start time),
0996##            'SSL_SERVER_V_END': Validity of server's certificate (end time),
0997            })
0998
0999        for prefix, dn in [("I", cert.get_issuer()),
1000                           ("S", cert.get_subject())]:
1001            # X509Name objects don't seem to have a way to get the
1002            # complete DN string. Use str() and slice it instead,
1003            # because str(dn) == "<X509Name object '/C=US/ST=...'>"
1004            dnstr = str(dn)[18:-2]
1005
1006            wsgikey = 'SSL_SERVER_%s_DN' % prefix
1007            self.ssl_environ[wsgikey] = dnstr
1008
1009            # The DN should be of the form: /k1=v1/k2=v2, but we must allow
1010            # for any value to contain slashes itself (in a URL).
1011            while dnstr:
1012                pos = dnstr.rfind("=")
1013                dnstr, value = dnstr[:pos], dnstr[pos + 1:]
1014                pos = dnstr.rfind("/")
1015                dnstr, key = dnstr[:pos], dnstr[pos + 1:]
1016                if key and value:
1017                    wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
1018                    self.ssl_environ[wsgikey] = value