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

Changeset 1711

Show
Ignore:
Timestamp:
08/25/07 15:07:06
Author:
fumanchu
Message:

Great progress on #718 (High count of uncollectable objects). Folded the separate InternalRedirect? WSGI middleware back into AppResponse?.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • branches/cherrypy-3.0.x/cherrypy/_cpwsgi.py

    r1608 r1711  
    99 
    1010 
    11 #                            Internal Redirect                            # 
    12  
    13  
    14 class InternalRedirector(object): 
    15     """WSGI middleware which handles cherrypy.InternalRedirect. 
    16      
    17     When cherrypy.InternalRedirect is raised, this middleware traps it, 
    18     rewrites the WSGI environ using the new path and query_string, 
    19     and calls the next application again. Because the wsgi.input stream 
    20     may have already been consumed by the next application, the redirected 
    21     call will always be of HTTP method "GET", and therefore any params must 
    22     be passed in the InternalRedirect object's query_string attribute. 
    23     If you need something more complicated, make and raise your own 
    24     exception and your own WSGI middleware to trap it. ;) 
    25      
    26     It would be a bad idea to raise InternalRedirect after you've already 
    27     yielded response content, although an enterprising soul could choose 
    28     to abuse this. 
    29      
    30     nextapp: the next application callable in the WSGI chain. 
    31      
    32     recursive: if False (the default), each URL (path + qs) will be 
    33     stored, and, if the same URL is requested again, RuntimeError will 
    34     be raised. If 'recursive' is True, no such error will be raised. 
     11#                           WSGI-to-CP Adapter                           # 
     12 
     13 
     14class AppResponse(object): 
     15    """A WSGI application for CherryPy Application responses. 
     16     
     17    recursive: if False (the default), each URL (path + qs) will be stored, 
     18    and, if the same URL is requested again via an InternalRedirect, 
     19    RuntimeError will be raised. If 'recursive' is True, no such error 
     20    will be raised. 
    3521    """ 
    3622     
    37     def __init__(self, nextapp, recursive=False): 
    38         self.nextapp = nextapp 
    39         self.recursive = recursive 
    40      
    41     def __call__(self, environ, start_response): 
    42         return IRResponse(self.nextapp, environ, start_response, self.recursive) 
    43  
    44  
    45 class IRResponse(object): 
    46      
    47     def __init__(self, nextapp, environ, start_response, recursive): 
     23    throws = (KeyboardInterrupt, SystemExit) 
     24    request = None 
     25     
     26    def __init__(self, environ, start_response, cpapp, recursive=False): 
    4827        self.redirections = [] 
    4928        self.recursive = recursive 
    50         self.environ = environ.copy() 
    51         self.nextapp = nextapp 
     29        self.environ = environ 
    5230        self.start_response = start_response 
    53         self.response = None 
    54         self.iter_response = None 
     31        self.cpapp = cpapp 
    5532        self.setapp() 
    5633     
    5734    def setapp(self): 
    58         while True: 
    59             try: 
    60                 self.response = self.nextapp(self.environ, self.start_response) 
    61                 self.iter_response = iter(self.response) 
    62                 return 
    63             except _cherrypy.InternalRedirect, ir: 
    64                 self.close() 
    65                 self.setenv(ir) 
    66      
    67     def setenv(self, ir): 
    68         env = self.environ 
    69         if not self.recursive: 
    70             if ir.path in self.redirections: 
    71                 raise RuntimeError("InternalRedirector visited the " 
    72                                    "same URL twice: %r" % ir.path) 
    73             else: 
    74                 # Add the *previous* path_info + qs to redirections. 
    75                 sn = env.get('SCRIPT_NAME', '') 
    76                 path = env.get('PATH_INFO', '') 
    77                 qs = env.get('QUERY_STRING', '') 
    78                 if qs: 
    79                     qs = "?" + qs 
    80                 self.redirections.append(sn + path + qs) 
    81          
    82         # Munge environment and try again. 
    83         env['REQUEST_METHOD'] = "GET" 
    84         env['PATH_INFO'] = ir.path 
    85         env['QUERY_STRING'] = ir.query_string 
    86         env['wsgi.input'] = _StringIO.StringIO() 
    87         env['CONTENT_LENGTH'] = "0" 
    88      
    89     def close(self): 
    90         if hasattr(self.response, "close"): 
    91             self.response.close() 
    92      
    93     def __iter__(self): 
    94         return self 
    95      
    96     def next(self): 
    97         while True: 
    98             try: 
    99                 return self.iter_response.next() 
    100             except _cherrypy.InternalRedirect, ir: 
    101                 self.close() 
    102                 self.setenv(ir) 
    103                 self.setapp() 
    104  
    105  
    106  
    107 #                           WSGI-to-CP Adapter                           # 
    108  
    109  
    110 class AppResponse(object): 
    111      
    112     throws = (KeyboardInterrupt, SystemExit, _cherrypy.InternalRedirect) 
    113     request = None 
    114      
    115     def __init__(self, environ, start_response, cpapp): 
    116         try: 
    117             self.request = self.get_engine_request(environ, cpapp) 
    118              
    119             meth = environ['REQUEST_METHOD'] 
    120             path = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '') 
    121             qs = environ.get('QUERY_STRING', '') 
    122             rproto = environ.get('SERVER_PROTOCOL') 
    123             headers = self.translate_headers(environ) 
    124             rfile = environ['wsgi.input'] 
    125              
    126             response = self.request.run(meth, path, qs, rproto, headers, rfile) 
    127             s, h, b = response.status, response.header_list, response.body 
     35        # Stage 1: by whatever means necessary, obtain a status, header 
     36        #          set and body, with an optional exception object. 
     37        try: 
     38            self.request = self.get_engine_request() 
     39            s, h, b = self.get_response() 
    12840            exc = None 
    12941        except self.throws: 
    13042            self.close() 
    13143            raise 
     44        except _cherrypy.InternalRedirect, ir: 
     45            self.environ['cherrypy.previous_request'] = _cherrypy._serving.request 
     46            self.close() 
     47            self.iredirect(ir.path, ir.query_string) 
     48            return 
    13249        except: 
    13350            if getattr(self.request, "throw_errors", False): 
     
    14461        self.iter_response = iter(b) 
    14562         
    146         try: 
    147             start_response(s, h, exc) 
     63        # Stage 2: now that we have a status, header set, and body, 
     64        #          finish the WSGI conversation by calling start_response. 
     65        try: 
     66            self.start_response(s, h, exc) 
    14867        except self.throws: 
    14968            self.close() 
     
    16281            s, h, b = _cperror.bare_error() 
    16382            self.iter_response = iter(b) 
     83     
     84    def iredirect(self, path, query_string): 
     85        """Doctor self.environ and perform an internal redirect. 
     86         
     87        When cherrypy.InternalRedirect is raised, this method is called. 
     88        It rewrites the WSGI environ using the new path and query_string, 
     89        and calls a new CherryPy Request object. Because the wsgi.input 
     90        stream may have already been consumed by the next application, 
     91        the redirected call will always be of HTTP method "GET"; therefore, 
     92        any params must be passed in the query_string argument, which is 
     93        formed from InternalRedirect.query_string when using that exception. 
     94        If you need something more complicated, make and raise your own 
     95        exception and write your own AppResponse subclass to trap it. ;) 
     96         
     97        It would be a bad idea to redirect after you've already yielded 
     98        response content, although an enterprising soul could choose 
     99        to abuse this. 
     100        """ 
     101        env = self.environ 
     102        if not self.recursive: 
     103            sn = env.get('SCRIPT_NAME', '') 
     104            qs = query_string 
     105            if qs: 
     106                qs = "?" + qs 
     107            if sn + path + qs in self.redirections: 
     108                raise RuntimeError("InternalRedirector visited the " 
     109                                   "same URL twice: %r + %r + %r" % 
     110                                   (sn, path, qs)) 
     111            else: 
     112                # Add the *previous* path_info + qs to redirections. 
     113                p = env.get('PATH_INFO', '') 
     114                qs = env.get('QUERY_STRING', '') 
     115                if qs: 
     116                    qs = "?" + qs 
     117                self.redirections.append(sn + p + qs) 
     118         
     119        # Munge environment and try again. 
     120        env['REQUEST_METHOD'] = "GET" 
     121        env['PATH_INFO'] = path 
     122        env['QUERY_STRING'] = query_string 
     123        env['wsgi.input'] = _StringIO.StringIO() 
     124        env['CONTENT_LENGTH'] = "0" 
     125         
     126        self.setapp() 
    164127     
    165128    def __iter__(self): 
     
    176139            return chunk 
    177140        except self.throws: 
     141            self.close() 
    178142            raise 
     143        except _cherrypy.InternalRedirect, ir: 
     144            self.environ['cherrypy.previous_request'] = _cherrypy._serving.request 
     145            self.close() 
     146            self.iredirect(ir.path, ir.query_string) 
    179147        except StopIteration: 
    180148            raise 
    181149        except: 
    182150            if getattr(self.request, "throw_errors", False): 
     151                self.close() 
    183152                raise 
    184153             
     
    195164        _cherrypy.engine.release() 
    196165     
    197     def get_engine_request(self, environ, cpapp): 
     166    def get_response(self): 
     167        """Grab a request object from the engine and return its response.""" 
     168        meth = self.environ['REQUEST_METHOD'] 
     169        path = (self.environ.get('SCRIPT_NAME', '') + 
     170                self.environ.get('PATH_INFO', '')) 
     171        qs = self.environ.get('QUERY_STRING', '') 
     172        rproto = self.environ.get('SERVER_PROTOCOL') 
     173        headers = self.translate_headers(self.environ) 
     174        rfile = self.environ['wsgi.input'] 
     175        response = self.request.run(meth, path, qs, rproto, headers, rfile) 
     176        return response.status, response.header_list, response.body 
     177     
     178    def get_engine_request(self): 
    198179        """Return a Request object from the CherryPy Engine using environ.""" 
    199         env = environ.get 
     180        env = self.environ.get 
    200181         
    201182        local = _http.Host('', int(env('SERVER_PORT', 80)), 
     
    212193        # Both IIS and Apache set REMOTE_USER, when possible. 
    213194        request.login = env('LOGON_USER') or env('REMOTE_USER') or None 
    214         request.multithread = environ['wsgi.multithread'] 
    215         request.multiprocess = environ['wsgi.multiprocess'] 
    216         request.wsgi_environ = environ 
    217         request.app = cpapp 
    218         request.prev = env('cherrypy.request') 
    219         environ['cherrypy.request'] = request 
     195        request.multithread = self.environ['wsgi.multithread'] 
     196        request.multiprocess = self.environ['wsgi.multiprocess'] 
     197        request.wsgi_environ = self.environ 
     198        request.app = self.cpapp 
     199        request.prev = env('cherrypy.previous_request', None) 
    220200        return request 
    221201     
     
    257237    """ 
    258238     
    259     pipeline = [('iredir', InternalRedirector)
     239    pipeline = [
    260240    head = None 
    261241    config = {} 
  • branches/cherrypy-3.0.x/cherrypy/test/benchmark.py

    r1589 r1711  
    101101    } 
    102102app = cherrypy.tree.mount(Root(), SCRIPT_NAME, appconf) 
    103 # Remove internalredirect (nastily on by default) 
    104 app.wsgiapp.pipeline = [] 
    105103 
    106104 
  • branches/cherrypy-3.0.x/cherrypy/test/test.py

    r1710 r1711  
    355355     
    356356    testList = [ 
    357         # Run refleak test ASAP to make debugging easier. 
    358         'test_refleaks', 
    359          
    360357        'test_proxy', 
    361358        'test_caching', 
     
    378375        'test_wsgiapps', 
    379376        'test_wsgi_ns', 
     377         
     378        # Run refleak test as late as possible to 
     379        # catch refleaks from all exercised tests. 
     380        'test_refleaks', 
    380381    ] 
    381382     
  • branches/cherrypy-3.0.x/cherrypy/test/test_refleaks.py

    r1710 r1711  
    2424         
    2525        def gc_stats(self): 
    26             return "%s %s %s %s" % (gc.collect(), 
    27                                     len(get_instances(_cprequest.Request)), 
    28                                     len(get_instances(_cprequest.Response)), 
    29                                     len(gc.get_referrers(data))) 
     26            output = [] 
     27             
     28            # Uncollectable garbage 
     29             
     30            # gc_collect isn't perfectly synchronous, because it may 
     31            # break reference cycles that then take time to fully 
     32            # finalize. Call it twice and hope for the best. 
     33            gc.collect() 
     34            unreachable = gc.collect() 
     35            if unreachable: 
     36                output.append("\n%s unreachable objects:" % unreachable) 
     37                trash = {} 
     38                for x in gc.garbage: 
     39                    trash[type(x)] = trash.get(type(x), 0) + 1 
     40                trash = [(v, k) for k, v in trash.iteritems()] 
     41                trash.sort() 
     42                for pair in trash: 
     43                    output.append("    " + repr(pair)) 
     44             
     45            # Request references 
     46            reqs = get_instances(_cprequest.Request) 
     47            lenreqs = len(reqs) 
     48            if lenreqs < 2: 
     49                output.append("\nMissing Request reference. Should be 1 in " 
     50                              "this request thread and 1 in the main thread.") 
     51            elif lenreqs > 2: 
     52                output.append("\nToo many Request references (%r)." % lenreqs) 
     53                for req in reqs: 
     54                    output.append("Referrers for %s:" % repr(req)) 
     55                    for ref in gc.get_referrers(req): 
     56                        if ref is not reqs: 
     57                            output.append("    %s" % repr(ref)) 
     58             
     59            # Response references 
     60            resps = get_instances(_cprequest.Response) 
     61            lenresps = len(resps) 
     62            if lenresps < 2: 
     63                output.append("\nMissing Response reference. Should be 1 in " 
     64                              "this request thread and 1 in the main thread.") 
     65            elif lenresps > 2: 
     66                output.append("\nToo many Response references (%r)." % lenresps) 
     67                for resp in resps: 
     68                    output.append("Referrers for %s:" % repr(resp)) 
     69                    for ref in gc.get_referrers(resp): 
     70                        if ref is not resps: 
     71                            output.append("    %s" % repr(ref)) 
     72             
     73            return "\n".join(output) 
    3074        gc_stats.exposed = True 
    31          
    32         def gc_objtypes(self): 
    33             data = {} 
    34             for x in gc.get_objects(): 
    35                 data[type(x)] = data.get(type(x), 0) + 1 
    36              
    37             data = [(v, k) for k, v in data.iteritems()] 
    38             data.sort() 
    39             return "\n".join([repr(pair) for pair in data]) 
    40         gc_objtypes.exposed = True 
    4175     
    4276    cherrypy.tree.mount(Root()) 
     
    6498         
    6599        self.getPage("/gc_stats") 
    66         self.assertBody("0 1 1 1") 
    67          
    68         # If gc_stats fails, choose "ignore" to see the type counts for 
    69         # all the unreachable objects in this body. 
    70         self.getPage("/gc_objtypes") 
    71100        self.assertBody("") 
    72101 

Hosted by WebFaction

Log in as guest/cpguest to create tickets