Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

Changeset 1573

Show
Ignore:
Timestamp:
12/28/06 14:10:07
Author:
fumanchu
Message:

Copied recent changes from trunk to _cpwsgiserver3.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • branches/cherrypy-2.x/cherrypy/_cpwsgiserver3.py

    r1520 r1573  
    1 """A high-speed, production ready, thread pooled, generic WSGI server.""" 
     1"""A high-speed, production ready, thread pooled, generic WSGI server. 
     2 
     3Simplest example on how to use this module directly 
     4(without using CherryPy's application machinery): 
     5 
     6    from cherrypy import wsgiserver 
     7     
     8    def my_crazy_app(environ, start_response): 
     9        status = '200 OK' 
     10        response_headers = [('Content-type','text/plain')] 
     11        start_response(status, response_headers) 
     12        return ['Hello world!\n'] 
     13     
     14    # Here we set our application to the script_name '/'  
     15    wsgi_apps = [('/', my_crazy_app)] 
     16     
     17    server = wsgiserver.CherryPyWSGIServer(('localhost', 8070), wsgi_apps, 
     18                                           server_name='localhost') 
     19     
     20    # Want SSL support? Just set these attributes 
     21    # server.ssl_certificate = <filename> 
     22    # server.ssl_private_key = <filename> 
     23     
     24    if __name__ == '__main__': 
     25        server.start() 
     26 
     27This won't call the CherryPy engine (application side) at all, only the 
     28WSGI server, which is independant from the rest of CherryPy. Don't 
     29let the name "CherryPyWSGIServer" throw you; the name merely reflects 
     30its origin, not it's coupling. 
     31 
     32The CherryPy WSGI server can serve as many WSGI application 
     33as you want in one instance: 
     34 
     35    wsgi_apps = [('/', my_crazy_app), (/blog', my_blog_app)] 
     36 
     37""" 
     38 
    239 
    340import base64 
    4 import mimetools # todo: use email 
    541import Queue 
    642import os 
     
    3975socket_errors_to_ignore.append("timed out") 
    4076 
    41 # These are lowercase because mimetools.Message uses lowercase keys. 
    42 comma_separated_headers = [ 
    43     'accept', 'accept-charset', 'accept-encoding', 'accept-language', 
    44     'accept-ranges', 'allow', 'cache-control', 'connection', 'content-encoding', 
    45     'content-language', 'expect', 'if-match', 'if-none-match', 'pragma', 
    46     'proxy-authenticate', 'te', 'trailer', 'transfer-encoding', 'upgrade', 
    47     'vary', 'via', 'warning', 'www-authenticate', 
    48     ] 
    49  
     77comma_separated_headers = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING', 
     78    'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL', 
     79    'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT', 
     80    'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE', 
     81    'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING', 
     82    'WWW-AUTHENTICATE'] 
    5083 
    5184class HTTPRequest(object): 
     85    """An HTTP Request (and response). 
     86     
     87    A single HTTP connection may consist of multiple request/response pairs. 
     88     
     89    connection: the HTTP Connection object which spawned this request. 
     90    rfile: the 'read' fileobject from the connection's socket 
     91    ready: when True, the request has been parsed and is ready to begin 
     92        generating the response. When False, signals the calling Connection 
     93        that the response should not be generated and the connection should 
     94        close. 
     95    close_connection: signals the calling Connection that the request 
     96        should close. This does not imply an error! The client and/or 
     97        server may each request that the connection be closed. 
     98    chunked_write: if True, output will be encoded with the "chunked" 
     99        transfer-coding. This value is set automatically inside 
     100        send_headers. 
     101    """ 
    52102     
    53103    def __init__(self, connection): 
    54104        self.connection = connection 
    55105        self.rfile = self.connection.rfile 
     106        self.sendall = self.connection.sendall 
    56107        self.environ = connection.environ.copy() 
    57108         
     
    65116     
    66117    def parse_request(self): 
     118        """Parse the next HTTP request start-line and message-headers.""" 
    67119        # HTTP/1.1 connections are persistent by default. If a client 
    68120        # requests a page, then idles (leaves the connection open), 
     
    78130            return 
    79131         
     132        if request_line == "\r\n": 
     133            # RFC 2616 sec 4.1: "...if the server is reading the protocol 
     134            # stream at the beginning of a message and receives a CRLF 
     135            # first, it should ignore the CRLF." 
     136            # But only ignore one leading line! else we enable a DoS. 
     137            request_line = self.rfile.readline() 
     138            if not request_line: 
     139                self.ready = False 
     140                return 
     141         
    80142        server = self.connection.server 
    81         self.environ["SERVER_SOFTWARE"] = "%s WSGI Server" % server.version 
     143        environ = self.environ 
     144        environ["SERVER_SOFTWARE"] = "%s WSGI Server" % server.version 
    82145         
    83146        method, path, req_protocol = request_line.strip().split(" ", 2) 
    84         self.environ["REQUEST_METHOD"] = method 
     147        environ["REQUEST_METHOD"] = method 
    85148         
    86149        # path may be an abs_path (including "http://host.domain.tld"); 
     
    93156         
    94157        if scheme: 
    95             self.environ["wsgi.url_scheme"] = scheme 
     158            environ["wsgi.url_scheme"] = scheme 
    96159        if params: 
    97160            path = path + ";" + params 
     
    109172            # This means, of course, that the last wsgi_app (shortest path) 
    110173            # will always handle a URI of "*". 
    111             self.environ["SCRIPT_NAME"] = "" 
    112             self.environ["PATH_INFO"] = "*" 
     174            environ["SCRIPT_NAME"] = "" 
     175            environ["PATH_INFO"] = "*" 
    113176            self.wsgi_app = server.mount_points[-1][1] 
    114177        else: 
     
    116179                # The mount_points list should be sorted by length, descending. 
    117180                if path.startswith(mount_point + "/") or path == mount_point: 
    118                     self.environ["SCRIPT_NAME"] = mount_point 
    119                     self.environ["PATH_INFO"] = path[len(mount_point):] 
     181                    environ["SCRIPT_NAME"] = mount_point 
     182                    environ["PATH_INFO"] = path[len(mount_point):] 
    120183                    self.wsgi_app = wsgi_app 
    121184                    break 
     
    126189        # Note that, like wsgiref and most other WSGI servers, 
    127190        # we unquote the path but not the query string. 
    128         self.environ["QUERY_STRING"] = qs 
     191        environ["QUERY_STRING"] = qs 
    129192         
    130193        # Compare request and server HTTP protocol versions, in case our 
     
    146209            return 
    147210        # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol. 
    148         self.environ["SERVER_PROTOCOL"] = req_protocol 
     211        environ["SERVER_PROTOCOL"] = req_protocol 
    149212        # set a non-standard environ entry so the WSGI app can know what 
    150213        # the *real* server protocol is (and what features to support). 
    151214        # See http://www.faqs.org/rfcs/rfc2145.html. 
    152         self.environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol 
     215        environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol 
    153216        self.response_protocol = "HTTP/%s.%s" % min(rp, sp) 
    154217         
    155218        # If the Request-URI was an absoluteURI, use its location atom. 
    156219        if location: 
    157             self.environ["SERVER_NAME"] = location 
     220            environ["SERVER_NAME"] = location 
    158221         
    159222        # then all the http headers 
    160         headers = mimetools.Message(self.rfile) 
    161         self.environ.update(self.parse_headers(headers)) 
    162          
    163         creds = headers.getheader("Authorization", "").split(" ", 1) 
    164         self.environ["AUTH_TYPE"] = creds[0] 
     223        try: 
     224            self.read_headers() 
     225        except ValueError, ex: 
     226            self.simple_response("400 Bad Request", repr(ex.args)) 
     227            return 
     228         
     229        creds = environ.get("HTTP_AUTHORIZATION", "").split(" ", 1) 
     230        environ["AUTH_TYPE"] = creds[0] 
    165231        if creds[0].lower() == 'basic': 
    166232            user, pw = base64.decodestring(creds[1]).split(":", 1) 
    167             self.environ["REMOTE_USER"] = user 
     233            environ["REMOTE_USER"] = user 
    168234         
    169235        # Persistent connection support 
    170236        if self.response_protocol == "HTTP/1.1": 
    171             if headers.getheader("Connection", "") == "close": 
     237            if environ.get("HTTP_CONNECTION", "") == "close": 
    172238                self.close_connection = True 
    173                 self.outheaders.append(("Connection", "close")) 
    174239        else: 
    175240            # HTTP/1.0 
    176             if headers.getheader("Connection", "") == "Keep-Alive": 
    177                 if self.close_connection == False: 
    178                     self.outheaders.append(("Connection", "Keep-Alive")) 
    179             else: 
     241            if environ.get("HTTP_CONNECTION", "") != "Keep-Alive": 
    180242                self.close_connection = True 
    181243         
    182244        # Transfer-Encoding support 
    183         te = headers.getheader("Transfer-Encoding", "") 
    184         te = [x.strip() for x in te.split(",") if x.strip()] 
     245        te = None 
     246        if self.response_protocol == "HTTP/1.1": 
     247            te = environ.get("HTTP_TRANSFER_ENCODING") 
     248            if te: 
     249                te = [x.strip().lower() for x in te.split(",") if x.strip()] 
     250         
     251        read_chunked = False 
     252         
    185253        if te: 
    186             while te: 
    187                 enc = te.pop() 
    188                 if enc.lower() == "chunked": 
    189                     if not self.decode_chunked(): 
    190                         return 
     254            for enc in te: 
     255                if enc == "chunked": 
     256                    read_chunked = True 
    191257                else: 
     258                    # Note that, even if we see "chunked", we must reject 
     259                    # if there is an extension we don't recognize. 
    192260                    self.simple_response("501 Unimplemented") 
    193261                    self.close_connection = True 
    194262                    return 
     263         
     264        if read_chunked: 
     265            if not self.decode_chunked(): 
     266                return 
    195267        else: 
    196             cl = headers.getheader("Content-length") 
     268            cl = environ.get("CONTENT_LENGTH") 
    197269            if method in ("POST", "PUT") and cl is None: 
    198270                # No Content-Length header supplied. This will hang 
     
    220292        # We used to do 3, but are now doing 1. Maybe we'll do 2 someday, 
    221293        # but it seems like it would be a big slowdown for such a rare case. 
    222         if headers.getheader("Expect", "") == "100-continue": 
     294        if environ.get("HTTP_EXPECT", "") == "100-continue": 
    223295            self.simple_response(100) 
    224296         
    225297        self.ready = True 
    226298     
    227     def parse_headers(self, headers): 
    228         environ = {} 
    229         ct = headers.getheader("Content-type", "") 
    230         if ct: 
    231             environ["CONTENT_TYPE"] = ct 
    232         cl = headers.getheader("Content-length") or "" 
    233         if cl: 
    234             environ["CONTENT_LENGTH"] = cl 
    235          
    236         # Must use keys() here for Python 2.3 (rfc822.Message had no __iter__). 
    237         for k in headers.keys(): 
    238             if k in ('transfer-encoding', 'content-type', 'content-length'): 
    239                 continue 
     299    def read_headers(self): 
     300        """Read header lines from the incoming stream.""" 
     301        environ = self.environ 
     302         
     303        while True: 
     304            line = self.rfile.readline() 
     305            if not line: 
     306                # No more data--illegal end of headers 
     307                raise ValueError("Illegal end of headers.") 
    240308             
    241             envname = "HTTP_" + k.upper().replace("-", "_") 
     309            if line == '\r\n': 
     310                # Normal end of headers 
     311                break 
     312             
     313            if line[0] in ' \t': 
     314                # It's a continuation line. 
     315                v = line.strip() 
     316            else: 
     317                k, v = line.split(":", 1) 
     318                k, v = k.strip().upper(), v.strip() 
     319                envname = "HTTP_" + k.replace("-", "_") 
     320             
    242321            if k in comma_separated_headers: 
    243322                existing = environ.get(envname) 
    244323                if existing: 
    245                     environ[envname] = ", ".join([existing] + headers.getheaders(k)) 
    246                 else: 
    247                     environ[envname] = ", ".join(headers.getheaders(k)) 
    248             else: 
    249                 environ[envname] = headers[k] 
    250         return environ 
     324                    v = ", ".join((existing, v)) 
     325            environ[envname] = v 
     326         
     327        ct = environ.pop("HTTP_CONTENT_TYPE", None) 
     328        if ct: 
     329            environ["CONTENT_TYPE"] = ct 
     330        cl = environ.pop("HTTP_CONTENT_LENGTH", None) 
     331        if cl: 
     332            environ["CONTENT_LENGTH"] = cl 
    251333     
    252334    def decode_chunked(self): 
     
    269351                return 
    270352         
    271         headers = mimetools.Message(self.rfile) 
    272         self.environ.update(self.parse_headers(headers)) 
     353        # Grab any trailer headers 
     354        self.read_headers() 
     355         
    273356        data.seek(0) 
    274357        self.environ["wsgi.input"] = data 
     
    277360     
    278361    def respond(self): 
    279         wfile = self.connection.wfile 
     362        """Call the appropriate WSGI app and write its iterable output.""" 
    280363        response = self.wsgi_app(self.environ, self.start_response) 
    281364        try: 
    282             for line in response: 
    283                 if not self.sent_headers: 
    284                     self.sent_headers = True 
    285                     self.send_headers() 
    286                 if self.chunked_write: 
    287                     wfile.write(hex(len(line))[2:]) 
    288                     wfile.write("\r\n") 
    289                     wfile.write(line) 
    290                     wfile.write("\r\n") 
    291                 else: 
    292                     wfile.write(line) 
    293                 wfile.flush() 
     365            for chunk in response: 
     366                self.write(chunk) 
    294367        finally: 
    295368            if hasattr(response, "close"): 
     
    300373            self.send_headers() 
    301374        if self.chunked_write: 
    302             wfile.write("0\r\n\r\n") 
    303             wfile.flush() 
     375            self.sendall("0\r\n\r\n") 
    304376     
    305377    def simple_response(self, status, msg=""): 
    306378        """Write a simple response back to the client.""" 
    307379        status = str(status) 
    308         wfile = self.connection.wfile 
    309         wfile.write("%s %s\r\n" % (self.connection.server.protocol, status)) 
    310         wfile.write("Content-Length: %s\r\n" % len(msg)) 
     380        buf = ["%s %s\r\n" % (self.connection.server.protocol, status), 
     381               "Content-Length: %s\r\n" % len(msg)] 
    311382         
    312383        if status[:3] == "413" and self.response_protocol == 'HTTP/1.1': 
    313384            # Request Entity Too Large 
    314385            self.close_connection = True 
    315             wfile.write("Connection: close\r\n") 
    316          
    317         wfile.write("\r\n") 
     386            buf.append("Connection: close\r\n") 
     387         
     388        buf.append("\r\n") 
    318389        if msg: 
    319             wfile.write(msg) 
    320         wfile.flush(
     390            buf.append(msg) 
     391        self.sendall("".join(buf)
    321392     
    322393    def start_response(self, status, headers, exc_info = None): 
     394        """WSGI callable to begin the HTTP response.""" 
    323395        if self.started_response: 
    324396            if not exc_info: 
     
    334406        return self.write 
    335407     
    336     def write(self, d): 
     408    def write(self, chunk): 
     409        """WSGI callable to write unbuffered data to the client. 
     410         
     411        This method is also used internally by start_response (to write 
     412        data from the iterable returned by the WSGI application). 
     413        """ 
    337414        if not self.sent_headers: 
    338415            self.sent_headers = True 
    339416            self.send_headers() 
    340         self.connection.wfile.write(d) 
    341         self.connection.wfile.flush() 
     417        if self.chunked_write: 
     418            buf = [hex(len(chunk))[2:], 
     419                   "\r\n", chunk, "\r\n"] 
     420            self.sendall("".join(buf)) 
     421        else: 
     422            self.sendall(chunk) 
    342423     
    343424    def send_headers(self): 
    344         hkeys = [key.lower() for (key, value) in self.outheaders] 
     425        """Assert, process, and send the HTTP response message-headers.""" 
     426        hkeys = [key.lower() for key, value in self.outheaders] 
    345427        status = int(self.status[:3]) 
    346428         
    347         if self.response_protocol == 'HTTP/1.1': 
    348             if status == 413: 
    349                 # Request Entity Too Large. Close conn to avoid garbage. 
    350                 self.close_connection = True 
    351             elif "content-length" not in hkeys: 
    352                 if status in (200, 203, 206): 
     429        if status == 413: 
     430            # Request Entity Too Large. Close conn to avoid garbage. 
     431            self.close_connection = True 
     432        elif "content-length" not in hkeys: 
     433            # "All 1xx (informational), 204 (no content), 
     434            # and 304 (not modified) responses MUST NOT 
     435            # include a message-body." So no point chunking. 
     436            if status < 200 or status in (204, 205, 304): 
     437                pass 
     438            else: 
     439                if self.response_protocol == 'HTTP/1.1': 
    353440                    # Use the chunked transfer-coding 
    354441                    self.chunked_write = True 
    355442                    self.outheaders.append(("Transfer-Encoding", "chunked")) 
    356                 # "All 1xx (informational), 204 (no content), 
    357                 # and 304 (not modified) responses MUST NOT 
    358                 # include a message-body." 
    359                 elif status >= 200 and status not in (204, 205, 304): 
    360                     # Close conn to determine transfer-length. 
     443                else: 
     444                    # Closing the conn is the only way to determine len. 
    361445                    self.close_connection = True 
    362446         
    363         if self.close_connection and "connection" not in hkeys: 
    364             self.outheaders.append(("Connection", "close")) 
     447        if "connection" not in hkeys: 
     448            if self.response_protocol == 'HTTP/1.1': 
     449                if self.close_connection: 
     450                    self.outheaders.append(("Connection", "close")) 
     451            else: 
     452                if not self.close_connection: 
     453                    self.outheaders.append(("Connection", "Keep-Alive")) 
    365454         
    366455        if "date" not in hkeys: 
     
    368457         
    369458        server = self.connection.server 
    370         wfile = self.connection.wfile 
    371459         
    372460        if "server" not in hkeys: 
    373461            self.outheaders.append(("Server", server.version)) 
    374462         
    375         wfile.write(server.protocol + " " + self.status + "\r\n") 
     463        buf = [server.protocol, " ", self.status, "\r\n"] 
    376464        try: 
    377             for k, v in self.outheaders: 
    378                 wfile.write(k + ": " + v + "\r\n") 
     465            buf += [k + ": " + v + "\r\n" for k, v in self.outheaders] 
    379466        except TypeError: 
    380467            if not isinstance(k, str): 
     
    384471            else: 
    385472                raise 
    386         wfile.write("\r\n") 
    387         wfile.flush() 
    388  
    389  
    390 def _ssl_wrap_method(method): 
     473        buf.append("\r\n") 
     474        self.sendall("".join(buf)) 
     475 
     476 
     477def _ssl_wrap_method(method, is_reader=False): 
     478    """Wrap the given method with SSL error-trapping. 
     479     
     480    is_reader: if False (the default), EOF errors will be raised. 
     481        If True, EOF errors will return "" (to emulate normal sockets). 
     482    """ 
    391483    def ssl_method_wrapper(self, *args, **kwargs): 
    392484##        print (id(self), method, args, kwargs) 
     
    396488                return method(self, *args, **kwargs) 
    397489            except (SSL.WantReadError, SSL.WantWriteError): 
    398                 # Sleep and try again 
     490                # Sleep and try again. This is dangerous, because it means 
     491                # the rest of the stack has no way of differentiating 
     492                # between a "new handshake" error and "client dropped". 
     493                # Note this isn't an endless loop: there's a timeout below. 
    399494                time.sleep(self.ssl_retry) 
    400495            except SSL.SysCallError, e: 
    401                 if e.args == (-1, 'Unexpected EOF'): 
     496                if is_reader and e.args == (-1, 'Unexpected EOF'): 
    402497                    return "" 
    403498                 
    404499                errno = e.args[0] 
    405                 if errno not in socket_errors_to_ignore: 
    406                     raise socket.error(errno) 
    407                  
    408                 return "" 
     500                if is_reader and errno in socket_errors_to_ignore: 
     501                    return "" 
     502                raise socket.error(errno) 
    409503            except SSL.Error, e: 
    410                 if e.args == (-1, 'Unexpected EOF'): 
     504                if is_reader and e.args == (-1, 'Unexpected EOF'): 
    411505                    return "" 
    412                 elif e.args[0][0][2] == 'ssl handshake failure': 
     506                if is_reader and e.args[0][0][2] == 'ssl handshake failure': 
    413507                    return "" 
    414                 else: 
    415                     raise 
     508                raise 
    416509            if time.time() - start > self.ssl_timeout: 
    417510                raise socket.timeout("timed out") 
     
    428521    write = _ssl_wrap_method(socket._fileobject.write) 
    429522    writelines = _ssl_wrap_method(socket._fileobject.writelines) 
    430     read = _ssl_wrap_method(socket._fileobject.read
    431     readline = _ssl_wrap_method(socket._fileobject.readline
    432     readlines = _ssl_wrap_method(socket._fileobject.readlines
     523    read = _ssl_wrap_method(socket._fileobject.read, is_reader=True
     524    readline = _ssl_wrap_method(socket._fileobject.readline, is_reader=True
     525    readlines = _ssl_wrap_method(socket._fileobject.readlines, is_reader=True
    433526 
    434527 
    435528class HTTPConnection(object): 
     529    """An HTTP connection (active socket). 
     530     
     531    socket: the raw socket object (usually TCP) for this connection. 
     532    addr: the "bind address" for the remote end of the socket. 
     533        For IP sockets, this is a tuple of (REMOTE_ADDR, REMOTE_PORT). 
     534        For UNIX domain sockets, this will be a string. 
     535    server: the HTTP Server for this Connection. Usually, the server 
     536        object possesses a passive (server) socket which spawns multiple, 
     537        active (client) sockets, one for each connection. 
     538     
     539    environ: a WSGI environ template. This will be copied for each request. 
     540    rfile: a fileobject for reading from the socket. 
     541    sendall: a function for writing (+ flush) to the socket. 
     542    """ 
    436543     
    437544    rbufsize = -1 
    438     wbufsize = -1 
    439545    RequestHandlerClass = HTTPRequest 
    440546    environ = {"wsgi.version": (1, 0), 
     
    455561         
    456562        if SSL and isinstance(sock, SSL.ConnectionType): 
     563            timeout = sock.gettimeout() 
    457564            self.rfile = SSL_fileobject(sock, "r", self.rbufsize) 
    458             self.wfile = SSL_fileobject(sock, "w", self.wbufsize) 
     565            self.rfile.ssl_timeout = timeout 
     566            self.sendall = _ssl_wrap_method(sock.sendall) 
    459567            self.environ["wsgi.url_scheme"] = "https" 
    460568            self.environ["HTTPS"] = "on" 
     
    463571                self.environ.update(sslenv) 
    464572        else: 
    465             self.rfile = self.socket.makefile("r", self.rbufsize) 
    466             self.wfile = self.socket.makefile("w", self.wbufsize) 
     573            self.rfile = sock.makefile("r", self.rbufsize) 
     574            self.sendall = sock.sendall 
    467575         
    468576        self.environ.update({"wsgi.input": self.rfile, 
     
    511619     
    512620    def close(self): 
     621        """Close the socket underlying this connection.""" 
    513622        self.rfile.close() 
    514         self.wfile.close() 
    515623        self.socket.close() 
    516624 
     
    528636 
    529637class WorkerThread(threading.Thread): 
     638    """Thread which continuously polls a Queue for Connection objects. 
     639     
     640    server: the HTTP Server which spawned this thread, and which owns the 
     641        Queue and is placing active connections into it. 
     642    ready: a simple flag for the calling server to know when this thread 
     643        has begun polling the Queue. 
     644     
     645    Due to the timing issues of polling a Queue, a WorkerThread does not 
     646    check its own 'ready' flag after it has started. To stop the thread, 
     647    it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue 
     648    (one for each running WorkerThread). 
     649    """ 
    530650     
    531651    def __init__(self, server): 
     
    551671 
    552672class SSLConnection: 
     673    """A thread-safe wrapper for an SSL.Connection. 
     674     
     675    *args: the arguments to create the wrapped SSL.Connection(*args). 
     676    """ 
     677     
    553678    def __init__(self, *args): 
    554679        self._ssl_conn = SSL.Connection(*args) 
     
    586711        specifies the maximum number of queued connections (default 5). 
    587712    timeout: the timeout in seconds for accepted connections (default 10). 
     713     
     714    protocol: the version string to write in the Status-Line of all 
     715        HTTP responses. For example, "HTTP/1.1" (the default). This 
     716        also limits the supported features used in the response. 
     717     
     718     
     719    SSL/HTTPS 
     720    --------- 
     721    The OpenSSL module must be importable for SSL functionality. 
     722    You can obtain it from http://pyopenssl.sourceforge.net/ 
     723     
     724    ssl_certificate: the filename of the server SSL certificate. 
     725    ssl_privatekey: the filename of the server's private key file. 
     726     
     727    If either of these is None (both are None by default), this server 
     728    will not use SSL. If both are given and are valid, they will be read 
     729    on server start and used in the SSL context for the listening socket. 
    588730    """ 
    589731     
    590732    protocol = "HTTP/1.1" 
    591     version = "CherryPy/3.0.0RC1
     733    version = "CherryPy/3.0.0
    592734    ready = False 
    593735    _interrupt = None 
     
    631773        # trap those exceptions in whatever code block calls start(). 
    632774        self._interrupt = None 
    633          
    634         def bind(family, type, proto=0): 
    635             """Create (or recreate) the actual socket object.""" 
    636             self.socket = socket.socket(family, type, proto) 
    637             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
    638             if self.ssl_certificate and self.ssl_private_key: 
    639                 if SSL is None: 
    640                     raise ImportError("You must install pyOpenSSL to use HTTPS.") 
    641                  
    642                 # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473 
    643                 ctx = SSL.Context(SSL.SSLv23_METHOD) 
    644                 ctx.use_privatekey_file(self.ssl_private_key) 
    645                 ctx.use_certificate_file(self.ssl_certificate) 
    646                 self.socket = SSLConnection(ctx, self.socket) 
    647                 self.populate_ssl_environ() 
    648             self.socket.bind(self.bind_addr) 
    649775         
    650776        # Select the appropriate socket 
     
    665791            # Get the correct address family for our host (allows IPv6 addresses) 
    666792            host, port = self.bind_addr 
     793            flags = 0 
     794            if host == '': 
     795                # Despite the socket module docs, using '' does not 
     796                # allow AI_PASSIVE to work. Passing None instead 
     797                # returns '0.0.0.0' like we want. In other words: 
     798                #     host    AI_PASSIVE     result 
     799                #      ''         Y         192.168.x.y 
     800                #      ''         N         192.168.x.y 
     801                #     None        Y         0.0.0.0 
     802                #     None        N         127.0.0.1 
     803                host = None 
     804                flags = socket.AI_PASSIVE 
    667805            try: 
    668806                info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, 
    669                                           socket.SOCK_STREAM
     807                                          socket.SOCK_STREAM, 0, flags
    670808            except socket.gaierror: 
    671809                # Probably a DNS issue. Assume IPv4. 
     
    677815            af, socktype, proto, canonname, sa = res 
    678816            try: 
    679                 bind(af, socktype, proto) 
     817                self.bind(af, socktype, proto) 
    680818            except socket.error, msg: 
    681819                if self.socket: 
     
    710848                raise self.interrupt 
    711849     
     850    def bind(self, family, type, proto=0): 
     851        """Create (or recreate) the actual socket object.""" 
     852        self.socket = socket.socket(family, type, proto) 
     853        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
     854##        self.socket.setsockopt(socket.SOL_SOCKET, socket.TCP_NODELAY, 1) 
     855        if self.ssl_certificate and self.ssl_private_key: 
     856            if SSL is None: 
     857                raise ImportError("You must install pyOpenSSL to use HTTPS.") 
     858             
     859            # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473 
     860            ctx = SSL.Context(SSL.SSLv23_METHOD) 
     861            ctx.use_privatekey_file(self.ssl_private_key) 
     862            ctx.use_certificate_file(self.ssl_certificate) 
     863            self.socket = SSLConnection(ctx, self.socket) 
     864            self.populate_ssl_environ() 
     865        self.socket.bind(self.bind_addr) 
     866     
    712867    def tick(self): 
     868        """Accept a new connection and put it on the Queue.""" 
    713869        try: 
    714870            s, addr = self.socket.accept() 
     
    740896        self.stop() 
    741897        self._interrupt = interrupt 
    742     interrupt = property(_get_interrupt, _set_interrupt) 
     898    interrupt = property(_get_interrupt, _set_interrupt, 
     899                         doc="Set this to an Exception instance to " 
     900                             "interrupt the server.") 
    743901     
    744902    def stop(self): 
     
    756914                        raise 
    757915                else: 
     916                    # Note that we're explicitly NOT using AI_PASSIVE, 
     917                    # here, because we want an actual IP to touch. 
     918                    # localhost won't work if we've bound to a public IP, 
     919                    # but it would if we bound to INADDR_ANY via host = ''. 
    758920                    for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, 
    759921                                                  socket.SOCK_STREAM): 

Hosted by WebFaction

Log in as guest/cpguest to create tickets