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

Changeset 1677

Show
Ignore:
Timestamp:
06/22/07 01:49:31
Author:
fumanchu
Message:

Fix for #698 (wsgiserver classes should pass environs, not the server object). Also moved multiapp dispatching into a separate piece of middleware.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/cherrypy/_cpwsgi.py

    r1671 r1677  
    416416        # We could just pass cherrypy.tree, but by passing tree.apps, 
    417417        # we get correct SCRIPT_NAMEs as early as possible. 
    418         s.__init__(self, bind_addr, _cherrypy.tree.apps.items()
     418        s.__init__(self, bind_addr, _cherrypy.tree.apps
    419419                   server.thread_pool, 
    420420                   server.socket_host, 
  • trunk/cherrypy/test/helper.py

    r1666 r1677  
    147147    apps.reverse() 
    148148    for s in cherrypy.server.httpservers: 
    149         s.mount_points = apps 
     149        s.wsgi_app.apps = apps 
    150150 
    151151def _run_test_suite_thread(moduleNames, conf): 
  • trunk/cherrypy/test/test_core.py

    r1666 r1677  
    837837        for key in ['Content-Length', 'Content-Type', 'Date', 
    838838                    'Expires', 'Location', 'Server']: 
    839             self.assertEqual(hnames.count(key), 1
     839            self.assertEqual(hnames.count(key), 1, self.headers
    840840         
    841841        if cherrypy.server.protocol_version == "HTTP/1.1": 
  • trunk/cherrypy/wsgiserver/__init__.py

    r1676 r1677  
    1212        return ['Hello world!\n'] 
    1313     
    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> 
     14    server = wsgiserver.CherryPyWSGIServer( 
     15                ('localhost', 8070), my_crazy_app, 
     16                server_name='localhost') 
     17     
     18The CherryPy WSGI server can serve as many WSGI applications  
     19as you want in one instance: 
     20     
     21    wsgi_apps = [('/', my_crazy_app), ('/blog', my_blog_app)] 
     22    server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 80), wsgi_apps) 
     23     
     24Want SSL support? Just set these attributes: 
     25     
     26    server.ssl_certificate = <filename> 
     27    server.ssl_private_key = <filename> 
    2328     
    2429    if __name__ == '__main__': 
     
    3237let the name "CherryPyWSGIServer" throw you; the name merely reflects 
    3338its origin, not it's coupling. 
    34  
    35 The CherryPy WSGI server can serve as many WSGI applications 
    36 as you want in one instance: 
    37  
    38     wsgi_apps = [('/', my_crazy_app), ('/blog', my_blog_app)] 
    39  
    4039""" 
    4140 
     
    8584    'WWW-AUTHENTICATE'] 
    8685 
     86 
     87class WSGIPathInfoDispatcher(object): 
     88    """A WSGI dispatcher for dispatch based on the PATH_INFO. 
     89     
     90    apps: a dict or list of (path_prefix, app) pairs. 
     91    """ 
     92     
     93    def __init__(self, apps): 
     94        try: 
     95            apps = apps.items() 
     96        except AttributeError: 
     97            pass 
     98         
     99        # Sort the apps by len(path), descending 
     100        apps.sort() 
     101        apps.reverse() 
     102         
     103        # The path_prefix strings must start, but not end, with a slash. 
     104        # Use "" instead of "/". 
     105        self.apps = [(p.rstrip("/"), a) for p, a in apps] 
     106     
     107    def __call__(self, environ, start_response): 
     108        path = environ["PATH_INFO"] or "/" 
     109        for p, app in self.apps: 
     110            # The apps list should be sorted by length, descending. 
     111            if path.startswith(p + "/") or path == p: 
     112                environ = environ.copy() 
     113                environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p 
     114                environ["PATH_INFO"] = path[len(p):] 
     115                return app(environ, start_response) 
     116         
     117        start_response('404 Not Found', [('Content-Type', 'text/plain'), 
     118                                         ('Content-Length', '0')]) 
     119        return [''] 
     120 
     121 
    87122class HTTPRequest(object): 
    88123    """An HTTP Request (and response). 
     
    90125    A single HTTP connection may consist of multiple request/response pairs. 
    91126     
    92     connection: the HTTP Connection object which spawned this request. 
    93     rfile: the 'read' fileobject from the connection's socket 
     127    rfile: the 'read' fileobject from the connection's socket. 
     128    sendall: the 'sendall' method from the connection's fileobject. 
     129    wsgi_app: the WSGI application to call. 
     130    environ: a partial WSGI environ (server and connection entries). 
     131        The caller MUST set the following entries: 
     132        * SERVER_SOFTWARE: the value to write in the "Server" response header. 
     133        * ACTUAL_SERVER_PROTOCOL: the value to write in the Status-Line of 
     134            the response. From RFC 2145: "An HTTP server SHOULD send a 
     135            response version equal to the highest version for which the 
     136            server is at least conditionally compliant, and whose major 
     137            version is less than or equal to the one received in the 
     138            request.  An HTTP server MUST NOT send a version for which 
     139            it is not at least conditionally compliant." 
     140     
     141    outheaders: a list of header tuples to write in the response. 
    94142    ready: when True, the request has been parsed and is ready to begin 
    95143        generating the response. When False, signals the calling Connection 
     
    104152    """ 
    105153     
    106     def __init__(self, connection): 
    107         self.connection = connection 
    108         self.rfile = self.connection.rfile 
    109         self.sendall = self.connection.sendall 
    110         self.environ = connection.environ.copy() 
     154    def __init__(self, rfile, sendall, environ, wsgi_app): 
     155        self.rfile = rfile 
     156        self.sendall = sendall 
     157        self.environ = environ.copy() 
     158        self.wsgi_app = wsgi_app 
    111159         
    112160        self.ready = False 
     
    143191                return 
    144192         
    145         server = self.connection.server 
    146193        environ = self.environ 
    147         environ["SERVER_SOFTWARE"] = "%s WSGI Server" % server.version 
    148194         
    149195        method, path, req_protocol = request_line.strip().split(" ", 2) 
     
    162208        if params: 
    163209            path = path + ";" + params 
     210         
     211        environ["SCRIPT_NAME"] = "" 
    164212         
    165213        # Unquote the path+params (e.g. "/this%20path" -> "this path"). 
     
    171219        atoms = [unquote(x) for x in quoted_slash.split(path)] 
    172220        path = "%2F".join(atoms) 
    173          
    174         if path == "*": 
    175             # This means, of course, that the last wsgi_app (shortest path) 
    176             # will always handle a URI of "*". 
    177             environ["SCRIPT_NAME"] = "" 
    178             environ["PATH_INFO"] = "*" 
    179             self.wsgi_app = server.mount_points[-1][1] 
    180         else: 
    181             for mount_point, wsgi_app in server.mount_points: 
    182                 # The mount_points list should be sorted by length, descending. 
    183                 if path.startswith(mount_point + "/") or path == mount_point: 
    184                     environ["SCRIPT_NAME"] = mount_point 
    185                     environ["PATH_INFO"] = path[len(mount_point):] 
    186                     self.wsgi_app = wsgi_app 
    187                     break 
    188             else: 
    189                 self.simple_response("404 Not Found") 
    190                 return 
     221        environ["PATH_INFO"] = path 
    191222         
    192223        # Note that, like wsgiref and most other WSGI servers, 
     
    207238        # only return 505 if the _major_ version is different. 
    208239        rp = int(req_protocol[5]), int(req_protocol[7]) 
    209         sp = int(server.protocol[5]), int(server.protocol[7]) 
     240        server_protocol = environ["ACTUAL_SERVER_PROTOCOL"] 
     241        sp = int(server_protocol[5]), int(server_protocol[7]) 
    210242        if sp[0] != rp[0]: 
    211243            self.simple_response("505 HTTP Version Not Supported") 
     
    213245        # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol. 
    214246        environ["SERVER_PROTOCOL"] = req_protocol 
    215         # set a non-standard environ entry so the WSGI app can know what 
    216         # the *real* server protocol is (and what features to support). 
    217         # See http://www.faqs.org/rfcs/rfc2145.html. 
    218         environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol 
    219247        self.response_protocol = "HTTP/%s.%s" % min(rp, sp) 
    220248         
     
    369397            if hasattr(response, "close"): 
    370398                response.close() 
    371         if (self.ready and not self.sent_headers 
    372                 and not self.connection.server.interrupt): 
     399        if (self.ready and not self.sent_headers): 
    373400            self.sent_headers = True 
    374401            self.send_headers() 
     
    379406        """Write a simple response back to the client.""" 
    380407        status = str(status) 
    381         buf = ["%s %s\r\n" % (self.connection.server.protocol, status), 
     408        buf = ["%s %s\r\n" % (self.environ['ACTUAL_SERVER_PROTOCOL'], status), 
    382409               "Content-Length: %s\r\n" % len(msg)] 
    383410         
     
    461488            self.outheaders.append(("Date", rfc822.formatdate())) 
    462489         
    463         server = self.connection.server 
    464          
    465490        if "server" not in hkeys: 
    466             self.outheaders.append(("Server", server.version)) 
    467          
    468         buf = [server.protocol, " ", self.status, "\r\n"] 
     491            self.outheaders.append(("Server", self.environ['SERVER_SOFTWARE'])) 
     492         
     493        buf = [self.environ['ACTUAL_SERVER_PROTOCOL'], " ", self.status, "\r\n"] 
    469494        try: 
    470495            buf += [k + ": " + v + "\r\n" for k, v in self.outheaders] 
     
    550575     
    551576    socket: the raw socket object (usually TCP) for this connection. 
    552     addr: the "bind address" for the remote end of the socket. 
    553         For IP sockets, this is a tuple of (REMOTE_ADDR, REMOTE_PORT). 
    554         For UNIX domain sockets, this will be a string. 
    555     server: the HTTP Server for this Connection. Usually, the server 
    556         object possesses a passive (server) socket which spawns multiple, 
    557         active (client) sockets, one for each connection. The server object 
    558         must possess the following attributes: 
    559          
    560           * server_name: to be placed in the WSGI environ. 
    561           * bind_addr: either a UNIX socket (str) or a (host, port) tuple. 
    562           * version: a string, like "CherryPy/3.1alpha" 
    563           * protocol: an HTTP version string, e.g. "HTTP/1.1" 
    564           * mount_points: a list of [(script_name, wsgi_app)] pairs 
    565           * interrupt: usually None. 
    566      
     577    wsgi_app: the WSGI application for this server/connection. 
    567578    environ: a WSGI environ template. This will be copied for each request. 
     579     
    568580    rfile: a fileobject for reading from the socket. 
    569581    sendall: a function for writing (+ flush) to the socket. 
     
    580592               } 
    581593     
    582     def __init__(self, sock, addr, server): 
     594    def __init__(self, sock, wsgi_app, environ): 
    583595        self.socket = sock 
    584         self.addr = addr 
    585         self.server = server 
     596        self.wsgi_app = wsgi_app 
    586597         
    587598        # Copy the class environ into self. 
    588599        self.environ = self.environ.copy() 
     600        self.environ.update(environ) 
    589601         
    590602        if SSL and isinstance(sock, SSL.ConnectionType): 
     
    602614            self.sendall = sock.sendall 
    603615         
    604         self.environ.update({"wsgi.input": self.rfile, 
    605                              "SERVER_NAME": self.server.server_name, 
    606                              }) 
    607          
    608         if isinstance(self.server.bind_addr, basestring): 
    609             # AF_UNIX. This isn't really allowed by WSGI, which doesn't 
    610             # address unix domain sockets. But it's better than nothing. 
    611             self.environ["SERVER_PORT"] = "" 
    612         else: 
    613             self.environ["SERVER_PORT"] = str(self.server.bind_addr[1]) 
    614             # optional values 
    615             # Until we do DNS lookups, omit REMOTE_HOST 
    616             self.environ["REMOTE_ADDR"] = self.addr[0] 
    617             self.environ["REMOTE_PORT"] = str(self.addr[1]) 
     616        self.environ["wsgi.input"] = self.rfile 
    618617     
    619618    def communicate(self): 
     
    625624                # get written to the previous request. 
    626625                req = None 
    627                 req = self.RequestHandlerClass(self) 
     626                req = self.RequestHandlerClass(self.rfile, self.sendall, 
     627                                               self.environ, self.wsgi_app) 
    628628                # This order of operations should guarantee correct pipelining. 
    629629                req.parse_request() 
     
    748748        For UNIX sockets, supply the filename as a string. 
    749749    wsgi_app: the WSGI 'application callable'; multiple WSGI applications 
    750         may be passed as (script_name, callable) pairs. Script_name values 
    751         should not end in a slash. If the script_name refers to the root 
    752         of the URI, it should be an empty string (not "/"). 
     750        may be passed as (path_prefix, app) pairs. 
    753751    numthreads: the number of worker threads to create (default 10). 
    754752    server_name: the string to set for WSGI's SERVER_NAME environ entry. 
     
    783781    _interrupt = None 
    784782    ConnectionClass = HTTPConnection 
     783    environ = {} 
    785784     
    786785    # Paths to certificate and private key files 
     
    795794            # We've been handed a single wsgi_app, in CP-2.1 style. 
    796795            # Assume it's mounted at "". 
    797             self.mount_points = [("", wsgi_app)] 
     796            self.wsgi_app = wsgi_app 
    798797        else: 
    799             # We've been handed a list of (mount_point, wsgi_app) tuples, 
     798            # We've been handed a list of (path_prefix, wsgi_app) tuples, 
    800799            # so that the server can call different wsgi_apps, and also 
    801800            # correctly set SCRIPT_NAME. 
    802             self.mount_points = [(mp.rstrip("/"), app) 
    803                                  for mp, app in wsgi_app] 
    804         self.mount_points.sort() 
    805         self.mount_points.reverse() 
     801            self.wsgi_app = WSGIPathInfoDispatcher(wsgi_app) 
    806802         
    807803        self.bind_addr = bind_addr 
     
    942938            if hasattr(s, 'settimeout'): 
    943939                s.settimeout(self.timeout) 
    944             conn = self.ConnectionClass(s, addr, self) 
     940             
     941            environ = self.environ.copy() 
     942            # SERVER_SOFTWARE is common for IIS. It's also helpful for 
     943            # us to pass a default value for the "Server" response header. 
     944            environ["SERVER_SOFTWARE"] = "%s WSGI Server" % self.version 
     945            # set a non-standard environ entry so the WSGI app can know what 
     946            # the *real* server protocol is (and what features to support). 
     947            # See http://www.faqs.org/rfcs/rfc2145.html. 
     948            environ["ACTUAL_SERVER_PROTOCOL"] = self.protocol 
     949            environ["SERVER_NAME"] = self.server_name 
     950             
     951            if isinstance(self.bind_addr, basestring): 
     952                # AF_UNIX. This isn't really allowed by WSGI, which doesn't 
     953                # address unix domain sockets. But it's better than nothing. 
     954                environ["SERVER_PORT"] = "" 
     955            else: 
     956                environ["SERVER_PORT"] = str(self.bind_addr[1]) 
     957                # optional values 
     958                # Until we do DNS lookups, omit REMOTE_HOST 
     959                environ["REMOTE_ADDR"] = addr[0] 
     960                environ["REMOTE_PORT"] = str(addr[1]) 
     961             
     962            conn = self.ConnectionClass(s, self.wsgi_app, environ) 
    945963            self.requests.put(conn) 
    946964        except socket.timeout: 

Hosted by WebFaction

Log in as guest/cpguest to create tickets