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

Changeset 1244

Show
Ignore:
Timestamp:
08/15/06 08:34:27
Author:
dowski
Message:

Created a branch for some WSGI related ideas that I have implemented.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • branches/cp3-wsgi-remix/__init__.py

    r1243 r1244  
    88from _cperror import WrongConfigValue, TimeoutError 
    99import config 
     10 
     11from _cpwsgi import Application 
    1012 
    1113import _cptools 
  • branches/cp3-wsgi-remix/_cprequest.py

    r1243 r1244  
    169169                self.app = cherrypy.tree.apps[r] 
    170170            else: 
    171                 self.script_name = self.app.script_name 
     171                self.script_name = self.wsgi_environ.get('SCRIPT_NAME', '') 
    172172             
    173173            # path_info should be the path from the 
    174174            # app root (script_name) to the handler. 
    175             self.path_info = self.path[len(self.script_name.rstrip("/")):] 
     175            self.path_info = self.wsgi_environ.get('PATH_INFO', '') 
    176176             
    177177            # Loop to allow for InternalRedirect. 
  • branches/cp3-wsgi-remix/_cptree.py

    r1156 r1244  
    1 import logging 
    2 import sys 
    3  
    4 from cherrypy import config 
    5  
    6  
    7 class Application: 
    8     """A CherryPy Application.""" 
    9      
    10     def __init__(self, root, script_name="", conf=None): 
    11         self.access_log = log = logging.getLogger("cherrypy.access.%s" % id(self)) 
    12         log.setLevel(logging.INFO) 
    13          
    14         self.error_log = log = logging.getLogger("cherrypy.error.%s" % id(self)) 
    15         log.setLevel(logging.DEBUG) 
    16          
    17         self.root = root 
    18         self.script_name = script_name 
    19         self.conf = {} 
    20         if conf: 
    21             self.merge(conf) 
    22      
    23     def merge(self, conf): 
    24         """Merge the given config into self.config.""" 
    25         config.merge(self.conf, conf) 
    26          
    27         # Create log handlers as specified in config. 
    28         rootconf = self.conf.get("/", {}) 
    29         config._configure_builtin_logging(rootconf, self.access_log, "log_access_file") 
    30         config._configure_builtin_logging(rootconf, self.error_log) 
    31      
    32     def guess_abs_path(self): 
    33         """Guess the absolute URL from server.socket_host and script_name. 
    34          
    35         When inside a request, the abs_path can be formed via: 
    36             cherrypy.request.base + (cherrypy.request.app.script_name or "/") 
    37          
    38         However, outside of the request we must guess, hoping the deployer 
    39         set socket_host and socket_port correctly. 
    40         """ 
    41         port = int(config.get('server.socket_port', 80)) 
    42         if port in (443, 8443): 
    43             scheme = "https://" 
    44         else: 
    45             scheme = "http://" 
    46         host = config.get('server.socket_host', '') 
    47         if port != 80: 
    48             host += ":%s" % port 
    49         return scheme + host + self.script_name 
     1from cherrypy._cperror import format_exc, bare_error 
     2from cherrypy._cpwsgi import Application, HostedWSGI 
     3from cherrypy import NotFound 
     4from cherrypy.lib import http 
    505 
    516 
    527class Tree: 
    53     """A registry of CherryPy applications, mounted at diverse points.""" 
     8    """A dispatcher of WSGI applications, mounted at diverse points.""" 
    549     
    5510    def __init__(self): 
    5611        self.apps = {} 
    5712     
    58     def mount(self, root, script_name="", conf=None): 
    59         """Mount a new app from a root object, script_name, and conf.""" 
     13    def mount(self, app, script_name="", conf=None, wrap=True): 
     14        """Mount a new app at script_name using configuration in conf. 
     15         
     16        An application can be one of: 
     17            1) A standard cherrypy.Application - left as is. 
     18            2) A "root" object - wrapped in an Application instance. 
     19            3) A  WSGI callable - optionally wrapped in a HostedWSGI instance. 
     20         
     21        If wrap == True, a WSGI callable will be wrapped in a cherrypy.Application 
     22        instance, allowing the use of tools with the WSGI application. 
     23        """ 
     24         
    6025        # Next line both 1) strips trailing slash and 2) maps "/" -> "". 
    6126        script_name = script_name.rstrip("/") 
    62         app = Application(root, script_name, conf) 
     27         
     28        # Leave Application objects alone 
     29        if isinstance(app, Application): 
     30            pass 
     31        # Handle "root" objects... 
     32        elif not callable(app): 
     33            app = Application(app, script_name, conf) 
     34        # Handle WSGI callables 
     35        elif callable(app) and wrap: 
     36            app = HostedWSGI(app) 
     37        # In all other cases leave the app intact (no wrapping) 
     38         
    6339        self.apps[script_name] = app 
    6440         
    6541        # If mounted at "", add favicon.ico 
    66         if script_name == "" and root and not hasattr(root, "favicon_ico"): 
     42        if script_name == "" and app and not hasattr(app, "favicon_ico"): 
    6743            import os 
    6844            from cherrypy import tools 
    6945            favicon = os.path.join(os.getcwd(), os.path.dirname(__file__), 
    7046                                   "favicon.ico") 
    71             root.favicon_ico = tools.staticfile.handler(favicon) 
     47            app.favicon_ico = tools.staticfile.handler(favicon) 
    7248         
    7349        return app 
     
    11187        return http.urljoin(script_name, path) 
    11288 
     89    def dispatch(self, environ, start_response): 
     90        """Dispatch to mounted WSGI applications.""" 
     91        script_name = environ.get("SCRIPT_NAME", '').rstrip('/') 
     92        path_info = environ.get("PATH_INFO", '') 
     93         
     94        mount_points = self.apps.keys() 
     95        mount_points.sort() 
     96        mount_points.reverse() 
     97         
     98        for mp in mount_points: 
     99            if path_info.startswith(mp): 
     100                environ['SCRIPT_NAME'] = script_name + mp 
     101                environ['PATH_INFO'] = path_info[len(mp):] 
     102                app = self.apps[mp] 
     103                return app(environ, start_response) 
     104        raise NotFound 
  • branches/cp3-wsgi-remix/_cpwsgi.py

    r1230 r1244  
    11"""A WSGI application interface (see PEP 333).""" 
    2  
     2import logging 
    33import sys 
     4 
    45import cherrypy 
    5 from cherrypy import _cpwsgiserver 
     6from cherrypy import _cpwsgiserver, config 
    67from cherrypy._cperror import format_exc, bare_error 
    78from cherrypy.lib import http 
     
    2627            yield translatedHeader, environ[cgiName] 
    2728 
    28  
    29 def _wsgi_callable(environ, start_response, app=None): 
    30     request = None 
    31     try: 
    32         env = environ.get 
    33         local = http.Host('', int(env('SERVER_PORT', 80)), 
    34                           env('SERVER_NAME', '')) 
    35         remote = http.Host(env('REMOTE_ADDR', ''), 
    36                            int(env('REMOTE_PORT', -1)), 
    37                            env('REMOTE_HOST', '')) 
    38         request = cherrypy.engine.request(local, remote, env('wsgi.url_scheme')) 
    39          
    40         # LOGON_USER is served by IIS, and is the name of the 
    41         # user after having been mapped to a local account. 
    42         # Both IIS and Apache set REMOTE_USER, when possible. 
    43         request.login = env('LOGON_USER') or env('REMOTE_USER') or None 
    44          
    45         request.multithread = environ['wsgi.multithread'] 
    46         request.multiprocess = environ['wsgi.multiprocess'] 
    47         request.wsgi_environ = environ 
    48          
    49         if app: 
    50             request.app = app 
    51          
    52         path = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '') 
    53         response = request.run(environ['REQUEST_METHOD'], path, 
    54                                environ.get('QUERY_STRING'), 
    55                                environ.get('SERVER_PROTOCOL'), 
    56                                translate_headers(environ), 
    57                                environ['wsgi.input']) 
    58         s, h, b = response.status, response.header_list, response.body 
    59         exc = None 
    60     except (KeyboardInterrupt, SystemExit), ex: 
     29def _init_request(environ): 
     30    """Initialize and return the cherrypy.request object.""" 
     31    env = environ.get 
     32    local = http.Host('', int(env('SERVER_PORT', 80)), 
     33                      env('SERVER_NAME', '')) 
     34    remote = http.Host(env('REMOTE_ADDR', ''), 
     35                       int(env('REMOTE_PORT', -1)), 
     36                       env('REMOTE_HOST', '')) 
     37    request = cherrypy.engine.request(local, remote, env('wsgi.url_scheme')) 
     38    return request 
     39 
     40class Application: 
     41    """A CherryPy WSGI Application.""" 
     42     
     43    def __init__(self, root, script_name="", conf=None): 
     44        self.access_log = log = logging.getLogger("cherrypy.access.%s" % id(self)) 
     45        log.setLevel(logging.INFO) 
     46         
     47        self.error_log = log = logging.getLogger("cherrypy.error.%s" % id(self)) 
     48        log.setLevel(logging.DEBUG) 
     49         
     50        self.root = root 
     51        self.script_name = script_name 
     52        self.conf = {} 
     53        if conf: 
     54            self.merge(conf) 
     55 
     56    def __call__(self, environ, start_response): 
     57        if not getattr(cherrypy.request, 'initialized', False): 
     58            request = _init_request(environ) 
     59        else: 
     60            request = cherrypy.request 
    6161        try: 
     62             
     63            env = environ.get 
     64            # LOGON_USER is served by IIS, and is the name of the 
     65            # user after having been mapped to a local account. 
     66            # Both IIS and Apache set REMOTE_USER, when possible. 
     67            request.login = env('LOGON_USER') or env('REMOTE_USER') or None 
     68             
     69            request.multithread = environ['wsgi.multithread'] 
     70            request.multiprocess = environ['wsgi.multiprocess'] 
     71            request.wsgi_environ = environ 
     72            request.app = self 
     73             
     74            path = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '') 
     75            response = request.run(environ['REQUEST_METHOD'], path, 
     76                                   environ.get('QUERY_STRING'), 
     77                                   environ.get('SERVER_PROTOCOL'), 
     78                                   translate_headers(environ), 
     79                                   environ['wsgi.input']) 
     80            s, h, b = response.status, response.header_list, response.body 
     81            exc = None 
     82        except (KeyboardInterrupt, SystemExit), ex: 
     83            try: 
     84                if request: 
     85                    request.close() 
     86            except: 
     87                cherrypy.log(traceback=True) 
     88            request = None 
     89            raise ex 
     90        except: 
     91            if cherrypy.config.get("throw_errors", False): 
     92                raise 
     93            tb = format_exc() 
     94            cherrypy.log(tb) 
     95            if not cherrypy.config.get("show_tracebacks", False): 
     96                tb = "" 
     97            s, h, b = bare_error(tb) 
     98            exc = sys.exc_info() 
     99         
     100        try: 
     101            start_response(s, h, exc) 
     102            for chunk in b: 
     103                # WSGI requires all data to be of type "str". This coercion should 
     104                # not take any time at all if chunk is already of type "str". 
     105                # If it's unicode, it could be a big performance hit (x ~500). 
     106                if not isinstance(chunk, str): 
     107                    chunk = chunk.encode("ISO-8859-1") 
     108                yield chunk 
    62109            if request: 
    63110                request.close() 
     111            request = None 
     112        except (KeyboardInterrupt, SystemExit), ex: 
     113            try: 
     114                if request: 
     115                    request.close() 
     116            except: 
     117                cherrypy.log(traceback=True) 
     118            request = None 
     119            raise ex 
    64120        except: 
    65121            cherrypy.log(traceback=True) 
    66         request = None 
    67         raise ex 
    68     except: 
    69         if cherrypy.config.get("throw_errors", False): 
    70             raise 
    71         tb = format_exc() 
    72         cherrypy.log(tb) 
    73         if not cherrypy.config.get("show_tracebacks", False): 
    74             tb = "" 
    75         s, h, b = bare_error(tb) 
    76         exc = sys.exc_info() 
    77      
    78     try: 
    79         start_response(s, h, exc) 
    80         for chunk in b: 
    81             # WSGI requires all data to be of type "str". This coercion should 
    82             # not take any time at all if chunk is already of type "str". 
    83             # If it's unicode, it could be a big performance hit (x ~500). 
    84             if not isinstance(chunk, str): 
    85                 chunk = chunk.encode("ISO-8859-1") 
    86             yield chunk 
    87         if request: 
    88             request.close() 
    89         request = None 
    90     except (KeyboardInterrupt, SystemExit), ex: 
    91         try: 
    92             if request: 
    93                 request.close() 
    94         except: 
    95             cherrypy.log(traceback=True) 
    96         request = None 
    97         raise ex 
    98     except: 
    99         cherrypy.log(traceback=True) 
    100         try: 
    101             if request: 
    102                 request.close() 
    103         except: 
    104             cherrypy.log(traceback=True) 
    105         request = None 
    106         s, h, b = bare_error() 
    107         # CherryPy test suite expects bare_error body to be output, 
    108         # so don't call start_response (which, according to PEP 333, 
    109         # may raise its own error at that point). 
    110         for chunk in b: 
    111             if not isinstance(chunk, str): 
    112                 chunk = chunk.encode("ISO-8859-1") 
    113             yield chunk 
    114  
    115 def wsgiApp(environ, start_response): 
    116     """The WSGI 'application object' for CherryPy. 
    117      
    118     Use this as the same WSGI callable for all your CP apps. 
    119     """ 
    120     return _wsgi_callable(environ, start_response) 
    121  
    122 def make_app(app): 
    123     """Factory for making separate WSGI 'application objects' for each CP app. 
    124      
    125     Example: 
    126         # 'app' will be a CherryPy application object 
    127         app = cherrypy.tree.mount(Root(), "/", localconf) 
    128          
    129         # 'wsgi_app' will be a WSGI application 
    130         wsgi_app = _cpwsgi.make_app(app) 
    131     """ 
    132     def single_app(environ, start_response): 
    133         return _wsgi_callable(environ, start_response, app) 
    134     return single_app 
    135  
    136  
     122            try: 
     123                if request: 
     124                    request.close() 
     125            except: 
     126                cherrypy.log(traceback=True) 
     127            request = None 
     128            s, h, b = bare_error() 
     129            # CherryPy test suite expects bare_error body to be output, 
     130            # so don't call start_response (which, according to PEP 333, 
     131            # may raise its own error at that point). 
     132            for chunk in b: 
     133                if not isinstance(chunk, str): 
     134                    chunk = chunk.encode("ISO-8859-1") 
     135                yield chunk 
     136     
     137    def merge(self, conf): 
     138        """Merge the given config into self.config.""" 
     139        config.merge(self.conf, conf) 
     140         
     141        # Create log handlers as specified in config. 
     142        rootconf = self.conf.get("/", {}) 
     143        config._configure_builtin_logging(rootconf, self.access_log, "log_access_file") 
     144        config._configure_builtin_logging(rootconf, self.error_log) 
     145     
     146    def guess_abs_path(self): 
     147        """Guess the absolute URL from server.socket_host and script_name. 
     148         
     149        When inside a request, the abs_path can be formed via: 
     150            cherrypy.request.base + (cherrypy.request.app.script_name or "/") 
     151         
     152        However, outside of the request we must guess, hoping the deployer 
     153        set socket_host and socket_port correctly. 
     154        """ 
     155        port = int(config.get('server.socket_port', 80)) 
     156        if port in (443, 8443): 
     157            scheme = "https://" 
     158        else: 
     159            scheme = "http://" 
     160        host = config.get('server.socket_host', '') 
     161        if port != 80: 
     162            host += ":%s" % port 
     163        return scheme + host + self.script_name 
     164 
     165class HostedWSGI(object): 
     166    def __init__(self, app): 
     167        self.app = app 
     168        self._cp_config = {'tools.wsgiapp.on': True, 
     169                           'tools.wsgiapp.app': app, 
     170                          } 
     171     
     172    def __call__(self, environ, start_response): 
     173        return self.app(environ, start_response) 
    137174 
    138175#                            Server components                            # 
     
    197234                         conf('server.socket_port')) 
    198235         
    199         apps = [(base, wsgiApp) for base in cherrypy.tree.apps] 
     236        app = cherrypy.tree.dispatch 
    200237         
    201238        s = _cpwsgiserver.CherryPyWSGIServer 
    202         s.__init__(self, bind_addr, apps
     239        s.__init__(self, bind_addr, app
    203240                   conf('server.thread_pool'), 
    204241                   conf('server.socket_host'), 
  • branches/cp3-wsgi-remix/_cpwsgiserver.py

    r1237 r1244  
    8888        path = "%2F".join(atoms) 
    8989         
    90         for mount_point, wsgi_app in self.server.mount_points: 
    91             if path == "*": 
    92                 # This means, of course, that the first wsgi_app will 
    93                 # always handle a URI of "*". 
    94                 self.environ["SCRIPT_NAME"] = "" 
    95                 self.environ["PATH_INFO"] = "*" 
    96                 self.wsgi_app = wsgi_app 
    97                 break 
    98             # The mount_points list should be sorted by length, descending. 
    99             if path.startswith(mount_point): 
    100                 self.environ["SCRIPT_NAME"] = mount_point 
    101                 self.environ["PATH_INFO"] = path[len(mount_point):] 
    102                 self.wsgi_app = wsgi_app 
    103                 break 
    104         else: 
    105             self.abort("404 Not Found") 
    106             return 
     90        self.wsgi_app = self.server.app 
     91        self.environ['SCRIPT_NAME'] = '' 
     92        self.environ['PATH_INFO'] = path 
    10793         
    10894        # Note that, like wsgiref and most other WSGI servers, 
     
    288274        self.requests = Queue.Queue(max) 
    289275         
    290         if callable(wsgi_app): 
    291             # We've been handed a single wsgi_app, in CP-2.1 style. 
    292             # Assume it's mounted at "". 
    293             self.mount_points = [("", wsgi_app)] 
    294         else: 
    295             # We've been handed a list of (mount_point, wsgi_app) tuples, 
    296             # so that the server can call different wsgi_apps, and also 
    297             # correctly set SCRIPT_NAME. 
    298             self.mount_points = wsgi_app 
    299         self.mount_points.sort() 
    300         self.mount_points.reverse() 
     276        self.app = wsgi_app 
    301277         
    302278        self.bind_addr = bind_addr 
  • branches/cp3-wsgi-remix/test/helper.py

    r1219 r1244  
    116116            setup() 
    117117         
    118         # The setup functions probably mounted new apps. 
    119         # Tell our server about them. 
    120         apps = [] 
    121         for base, app in cherrypy.tree.apps.iteritems(): 
    122             if base == "/": 
    123                 base = "" 
    124             if conf.get("profiling.on", False): 
    125                 apps.append((base, profiler.make_app(_cpwsgi.wsgiApp))) 
    126 ##                apps.append((base, profiler.make_app(_cpwsgi.wsgiApp, aggregate=True))) 
    127             else: 
    128                 apps.append((base, _cpwsgi.wsgiApp)) 
    129 ##            # We could use the following line, but it breaks test_tutorials 
    130 ##            apps.append((base, _cpwsgi.make_app(app))) 
    131         apps.sort() 
    132         apps.reverse() 
    133118        for s in cherrypy.server.httpservers: 
    134             s.mount_points = apps 
     119            s.app = cherrypy.tree.dispatch 
    135120         
    136121        suite = CPTestLoader.loadTestsFromName(testmod) 
  • branches/cp3-wsgi-remix/test/test.py

    r1171 r1244  
    322322##        'test_states', 
    323323        'test_xmlrpc', 
    324         'test_wsgiapp', 
     324        'test_wsgiapps', 
    325325    ] 
    326326    CommandLineParser(testList).run() 
  • branches/cp3-wsgi-remix/test/test_wsgiapps.py

    r1243 r1244  
    1919        for k in keys: 
    2020            yield '%s: %s\n' % (k,environ[k]) 
     21 
     22    def reversing_middleware(app): 
     23        def _app(environ, start_response): 
     24            results = app(environ, start_response) 
     25            if not isinstance(results, basestring): 
     26                results = "".join(results) 
     27            results = list(results) 
     28            results.reverse() 
     29            return "".join(results) 
     30        return _app 
    2131     
    2232    class Root: 
     
    4252                         }} 
    4353    cherrypy.tree.mount(HostedWSGI(), '/hosted/app0', conf0) 
    44  
     54    cherrypy.tree.mount(test_app, '/hosted/app1', wrap=False) 
     55     
     56    app = reversing_middleware(cherrypy.Application(Root())) 
     57    cherrypy.tree.mount(app, '/hosted/app2', wrap=False) 
    4558 
    4659import helper 
     
    5669        self.assertBody("I'm a regular CherryPy page handler!") 
    5770     
    58     def test_02_tools(self): 
     71    def test_02_wrapped_wsgi(self): 
    5972        self.getPage("/hosted/app0") 
    6073        self.assertHeader("Content-Type", "text/plain") 
    6174        self.assertInBody(self.wsgi_output) 
    6275     
    63     def test_04_static_subdir(self): 
     76    def test_03_static_subdir(self): 
    6477        self.getPage("/hosted/app0/static/index.html") 
    6578        self.assertStatus('200 OK') 
    6679        self.assertHeader('Content-Type', 'text/html') 
    6780        self.assertBody('Hello, world\r\n') 
     81     
     82    def test_04_pure_wsgi(self): 
     83        self.getPage("/hosted/app1") 
     84        self.assertHeader("Content-Type", "text/plain") 
     85        self.assertInBody(self.wsgi_output) 
     86 
     87    def test_05_wrapped_cp_app(self): 
     88        self.getPage("/hosted/app2/") 
     89        body = list("I'm a regular CherryPy page handler!") 
     90        body.reverse() 
     91        body = "".join(body) 
     92        self.assertInBody(body) 
    6893 
    6994if __name__ == '__main__': 

Hosted by WebFaction

Log in as guest/cpguest to create tickets