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

Changeset 1430

Show
Ignore:
Timestamp:
11/16/06 13:10:21
Author:
fumanchu
Message:

Transformed _cptree.wsgi_handler function into _cpwsgi.CPWSGIApp class (a delegate for the Application object), and merged in the wsgi.pipeline functionality. This removes the need to call wsgi.pipeline(app) before tree.mount.

Files:

Legend:

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

    r1420 r1430  
    22 
    33import os 
    4 import sys 
    54import cherrypy 
    6 from cherrypy import _cpconfig, _cplogging, tools 
    7 from cherrypy._cperror import format_exc, bare_error 
    8 from cherrypy.lib import http 
     5from cherrypy import _cpconfig, _cplogging, _cpwsgi, tools 
    96 
    107 
     
    3027        self.root = root 
    3128        self.script_name = script_name 
     29        self.wsgiapp = _cpwsgi.CPWSGIApp(self) 
    3230        self.namespaces = {"log": lambda k, v: setattr(self.log, k, v), 
     31                           "wsgi": self.wsgiapp.namespace_handler, 
    3332                           } 
    3433        self.config = {} 
     
    5049        _cpconfig._call_namespaces(self.config.get("/", {}), self.namespaces) 
    5150     
    52     def wsgiapp(self, environ, start_response): 
    53         # This is here instead of __call__ because it's really hard 
    54         # to overwrite special methods like __call__ per instance. 
    55         return wsgi_handler(environ, start_response, app=self) 
    56      
    5751    def __call__(self, environ, start_response): 
    5852        return self.wsgiapp(environ, start_response) 
    59  
    60  
    61 headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization', 
    62                'CONTENT_LENGTH': 'Content-Length', 
    63                'CONTENT_TYPE': 'Content-Type', 
    64                'REMOTE_HOST': 'Remote-Host', 
    65                'REMOTE_ADDR': 'Remote-Addr', 
    66                } 
    67  
    68 def translate_headers(environ): 
    69     """Translate CGI-environ header names to HTTP header names.""" 
    70     for cgiName in environ: 
    71         # We assume all incoming header keys are uppercase already. 
    72         if cgiName in headerNames: 
    73             yield headerNames[cgiName], environ[cgiName] 
    74         elif cgiName[:5] == "HTTP_": 
    75             # Hackish attempt at recovering original header names. 
    76             translatedHeader = cgiName[5:].replace("_", "-") 
    77             yield translatedHeader, environ[cgiName] 
    78  
    79  
    80 def wsgi_handler(environ, start_response, app): 
    81     request = None 
    82     try: 
    83         env = environ.get 
    84         local = http.Host('', int(env('SERVER_PORT', 80)), 
    85                           env('SERVER_NAME', '')) 
    86         remote = http.Host(env('REMOTE_ADDR', ''), 
    87                            int(env('REMOTE_PORT', -1)), 
    88                            env('REMOTE_HOST', '')) 
    89         request = cherrypy.engine.request(local, remote, 
    90                                           env('wsgi.url_scheme'), 
    91                                           env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1")) 
    92          
    93         # LOGON_USER is served by IIS, and is the name of the 
    94         # user after having been mapped to a local account. 
    95         # Both IIS and Apache set REMOTE_USER, when possible. 
    96         request.login = env('LOGON_USER') or env('REMOTE_USER') or None 
    97          
    98         request.multithread = environ['wsgi.multithread'] 
    99         request.multiprocess = environ['wsgi.multiprocess'] 
    100         request.wsgi_environ = environ 
    101          
    102         request.app = app 
    103          
    104         path = env('SCRIPT_NAME', '') + env('PATH_INFO', '') 
    105         response = request.run(environ['REQUEST_METHOD'], path, 
    106                                env('QUERY_STRING', ''), 
    107                                env('SERVER_PROTOCOL'), 
    108                                translate_headers(environ), 
    109                                environ['wsgi.input']) 
    110         s, h, b = response.status, response.header_list, response.body 
    111         exc = 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 
    120     except: 
    121         if request and request.throw_errors: 
    122             raise 
    123         tb = format_exc() 
    124         cherrypy.log(tb) 
    125         if request and not request.show_tracebacks: 
    126             tb = "" 
    127         s, h, b = bare_error(tb) 
    128         exc = sys.exc_info() 
    129      
    130     try: 
    131         start_response(s, h, exc) 
    132         for chunk in b: 
    133             # WSGI requires all data to be of type "str". This coercion should 
    134             # not take any time at all if chunk is already of type "str". 
    135             # If it's unicode, it could be a big performance hit (x ~500). 
    136             if not isinstance(chunk, str): 
    137                 chunk = chunk.encode("ISO-8859-1") 
    138             yield chunk 
    139         if request: 
    140             request.close() 
    141         request = None 
    142     except (KeyboardInterrupt, SystemExit), ex: 
    143         try: 
    144             if request: 
    145                 request.close() 
    146         except: 
    147             cherrypy.log(traceback=True) 
    148         request = None 
    149         raise ex 
    150     except: 
    151         cherrypy.log(traceback=True) 
    152         try: 
    153             if request: 
    154                 request.close() 
    155         except: 
    156             cherrypy.log(traceback=True) 
    157         request = None 
    158         s, h, b = bare_error() 
    159         # CherryPy test suite expects bare_error body to be output, 
    160         # so don't call start_response (which, according to PEP 333, 
    161         # may raise its own error at that point). 
    162         for chunk in b: 
    163             if not isinstance(chunk, str): 
    164                 chunk = chunk.encode("ISO-8859-1") 
    165             yield chunk 
    16653 
    16754 
  • trunk/cherrypy/_cpwsgi.py

    r1394 r1430  
    11"""WSGI interface (see PEP 333).""" 
    22 
     3import sys as _sys 
     4 
    35import cherrypy as _cherrypy 
    4 from cherrypy import _cpwsgiserver 
     6from cherrypy import _cperror, _cpwsgiserver 
    57from cherrypy.lib import http as _http 
    68 
    79 
    8 class pipeline(list): 
    9     """An ordered list of configurable WSGI middleware
    10      
    11     self: a list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a 
    12         constructor that takes an initial, positional wsgiapp argument, 
     10class CPWSGIApp(object): 
     11    """A WSGI application object for a CherryPy Application
     12     
     13    pipeline: a list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a 
     14        constructor that takes an initial, positional 'nextapp' argument, 
    1315        plus optional keyword arguments, and returns a WSGI application 
    1416        (that takes environ and start_response arguments). The 'name' can 
    1517        be any you choose, and will correspond to keys in self.config. 
     18     
     19    head: rather than nest all apps in the pipeline on each call, it's only 
     20        done the first time, and the result is memoized into self.head. Set 
     21        this to None again if you change self.pipeline after calling self. 
     22     
    1623    config: a dict whose keys match names listed in the pipeline. Each 
    1724        value is a further dict which will be passed to the corresponding 
     
    1926    """ 
    2027     
    21     def __new__(cls, app, members=None, key="wsgi"): 
    22         return list.__new__(cls) 
    23      
    24     def __init__(self, app, members=None, key="wsgi"): 
    25         self.app = app 
    26         if members: 
    27             self.extend(members) 
    28         self.head = None 
    29         self.tail = None 
    30         self.config = {} 
    31         self.key = key 
    32         app.namespaces[key] = self.namespace_handler 
    33         app.wsgi_pipeline = self 
     28    pipeline = [] 
     29    head = None 
     30    config = {} 
     31     
     32    throws = (KeyboardInterrupt, SystemExit) 
     33     
     34    headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization', 
     35                   'CONTENT_LENGTH': 'Content-Length', 
     36                   'CONTENT_TYPE': 'Content-Type', 
     37                   'REMOTE_HOST': 'Remote-Host', 
     38                   'REMOTE_ADDR': 'Remote-Addr', 
     39                   } 
     40     
     41    def translate_headers(self, environ): 
     42        """Translate CGI-environ header names to HTTP header names.""" 
     43        for cgiName in environ: 
     44            # We assume all incoming header keys are uppercase already. 
     45            if cgiName in self.headerNames: 
     46                yield self.headerNames[cgiName], environ[cgiName] 
     47            elif cgiName[:5] == "HTTP_": 
     48                # Hackish attempt at recovering original header names. 
     49                translatedHeader = cgiName[5:].replace("_", "-") 
     50                yield translatedHeader, environ[cgiName] 
     51     
     52    def __init__(self, cpapp, pipeline=None): 
     53        self.cpapp = cpapp 
     54        self.pipeline = self.pipeline[:] 
     55        if pipeline: 
     56            self.pipeline.extend(pipeline) 
     57        self.config = self.config.copy() 
     58     
     59    def get_request(self, environ): 
     60        env = environ.get 
     61         
     62        local = _http.Host('', int(env('SERVER_PORT', 80)), 
     63                           env('SERVER_NAME', '')) 
     64        remote = _http.Host(env('REMOTE_ADDR', ''), 
     65                            int(env('REMOTE_PORT', -1)), 
     66                            env('REMOTE_HOST', '')) 
     67        scheme = env('wsgi.url_scheme') 
     68        sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1") 
     69        request = _cherrypy.engine.request(local, remote, scheme, sproto) 
     70         
     71        # LOGON_USER is served by IIS, and is the name of the 
     72        # user after having been mapped to a local account. 
     73        # Both IIS and Apache set REMOTE_USER, when possible. 
     74        request.login = env('LOGON_USER') or env('REMOTE_USER') or None 
     75        request.multithread = environ['wsgi.multithread'] 
     76        request.multiprocess = environ['wsgi.multiprocess'] 
     77        request.wsgi_environ = environ 
     78        request.app = self.cpapp 
     79        return request 
     80     
     81    def tail(self, environ, start_response): 
     82        """WSGI application callable for the actual CherryPy application. 
     83         
     84        You probably shouldn't call this; call self.__call__ instead, 
     85        so that any WSGI middleware in self.pipeline can run first. 
     86        """ 
     87        request = None 
     88        try: 
     89            request = self.get_request(environ) 
     90             
     91            meth = environ['REQUEST_METHOD'] 
     92            path = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '') 
     93            qs = environ.get('QUERY_STRING', '') 
     94            rproto = environ.get('SERVER_PROTOCOL') 
     95            headers = self.translate_headers(environ) 
     96            rfile = environ['wsgi.input'] 
     97             
     98            response = request.run(meth, path, qs, rproto, headers, rfile) 
     99            s, h, b = response.status, response.header_list, response.body 
     100            exc = None 
     101        except self.throws, ex: 
     102            self._close_req(request) 
     103            raise ex 
     104        except: 
     105            if request and request.throw_errors: 
     106                raise 
     107             
     108            tb = _cperror.format_exc() 
     109            _cherrypy.log(tb) 
     110            if request and not request.show_tracebacks: 
     111                tb = "" 
     112            s, h, b = _cperror.bare_error(tb) 
     113             
     114            exc = _sys.exc_info() 
     115         
     116        try: 
     117            start_response(s, h, exc) 
     118            for chunk in b: 
     119                # WSGI requires all data to be of type "str". This coercion should 
     120                # not take any time at all if chunk is already of type "str". 
     121                # If it's unicode, it could be a big performance hit (x ~500). 
     122                if not isinstance(chunk, str): 
     123                    chunk = chunk.encode("ISO-8859-1") 
     124                yield chunk 
     125            self._close_req(request) 
     126        except self.throws, ex: 
     127            self._close_req(request) 
     128            raise ex 
     129        except: 
     130            _cherrypy.log(traceback=True) 
     131            self._close_req(request) 
     132             
     133            # CherryPy test suite expects bare_error body to be output, 
     134            # so don't call start_response (which, according to PEP 333, 
     135            # may raise its own error at that point). 
     136            s, h, b = _cperror.bare_error() 
     137            for chunk in b: 
     138                if not isinstance(chunk, str): 
     139                    chunk = chunk.encode("ISO-8859-1") 
     140                yield chunk 
     141     
     142    def _close_req(self, request): 
     143        if hasattr(request, "close"): 
     144            try: 
     145                request.close() 
     146            except: 
     147                _cherrypy.log(traceback=True) 
     148     
     149    def __call__(self, environ, start_response): 
     150        head = self.head 
     151        if head is None: 
     152            # Create and nest the WSGI apps in our pipeline (in reverse order). 
     153            head = self.tail 
     154            for name, callable in self.pipeline[::-1]: 
     155                conf = self.config.get(name, {}) 
     156                head = callable(head, **conf) 
     157            self.head = head 
     158        return head(environ, start_response) 
    34159     
    35160    def namespace_handler(self, k, v): 
    36         """Config handler for our namespace.""" 
     161        """Config handler for the 'wsgi' namespace.""" 
    37162        if k == "pipeline": 
    38             # Note this allows multiple entries to be aggregated (but also 
    39             # note dicts are essentially unordered). It should also allow 
    40             # developers to set default middleware in code (passed to 
    41             # pipeline.__init__) that deployers can add to but not remove. 
    42             self.extend(v) 
    43              
    44             if self: 
    45                 # If self is empty, there's no need to replace app.wsgiapp. 
    46                 # Also note we're grabbing app.wsgiapp, not app.__call__, 
    47                 # so we can "play nice" with other Application-manglers 
    48                 # (hopefully, they'll do the same). 
    49                 self.tail = self.app.wsgiapp 
    50                 self.app.wsgiapp = self.__call__ 
     163            # Note this allows multiple 'wsgi.pipeline' config entries 
     164            # (but each entry will be processed in a 'random' order). 
     165            # It should also allow developers to set default middleware 
     166            # in code (passed to self.__init__) that deployers can add to 
     167            # (but not remove) via config. 
     168            self.pipeline.extend(v) 
    51169        else: 
    52170            name, arg = k.split(".", 1) 
    53171            bucket = self.config.setdefault(name, {}) 
    54172            bucket[arg] = v 
    55      
    56     def __call__(self, environ, start_response): 
    57         if not self.head: 
    58             # This class may be used without calling namespace_handler, 
    59             # in which case self.tail may still be None. 
    60             self.head = self.tail or self.app.wsgiapp 
    61             pipe = self[:] 
    62             pipe.reverse() 
    63             for name, callable in pipe: 
    64                 conf = self.config.get(name, {}) 
    65                 self.head = callable(self.head, **conf) 
    66         return self.head(environ, start_response) 
    67      
    68     def __repr__(self): 
    69         return "%s.%s(%r)" % (self.__module__, self.__class__.__name__, 
    70                               list(self)) 
    71173 
    72174 
  • trunk/cherrypy/test/test_wsgi_ns.py

    r1418 r1430  
    3939     
    4040    app = cherrypy.Application(Root()) 
    41     p = cherrypy.wsgi.pipeline(app, [('changecase', ChangeCase)]
    42     p.config['changecase'] = {'to': 'upper'} 
     41    app.wsgiapp.pipeline.append(('changecase', ChangeCase)
     42    app.wsgiapp.config['changecase'] = {'to': 'upper'} 
    4343    cherrypy.tree.mount(app, config={'/': root_conf}) 
    44      
    45     # If we do not supply any middleware in code, pipeline is much cleaner: 
    46     # app = cherrypy.Application(Root()) 
    47     # cherrypy.wsgi.pipeline(app) 
    48     # cherrypy.tree.mount(app, config={'/': root_conf}) 
    4944 
    5045 

Hosted by WebFaction

Log in as guest/cpguest to create tickets