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

Changeset 1433

Show
Ignore:
Timestamp:
11/16/06 18:29:52
Author:
fumanchu
Message:

InternalRedirect? changes:

  1. Moved InternalRedirect? out of the Request object, so that an IR creates a separate Request object per redirect. This makes the design of hooks and tools (both builtin and user-defined) much simpler and safer.
  2. New _cpwsgi.InternalRedirector? for the WSGI implementation. Users who do not use InternalRedirects? at all can remove this from the wsgi pipeline if they wish.
  3. InternalRedirect? trap implemented in _cpmodpy.py
  4. Custom servers/gateways will have to write their own handling for InternalRedirect? being thrown from request.run.
  5. Request.redirections (a list of URL's) removed in favor of request.prev, which points to the previous Request object in a redirection chain. Defaults to None.
  6. Replace "request.recursive" (per request) with "wsgi.iredir.recursive" (per app).
  7. Removed Request.body_read and .headers_read.
  8. New Request.throws tuple of exceptions which should not be trapped.
Files:

Legend:

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

    r1400 r1433  
    1010    pass 
    1111 
     12 
    1213class InternalRedirect(CherryPyException): 
    1314    """Exception raised to switch to the handler for a different URL. 
    1415     
    15     If you supply a query string, it will replace request.params. 
    16     If you omit the query string (no question mark), the params from the 
    17     original request will remain in effect. 
    18     To erase any query string parameters, write url + "?" with no params. 
     16    Any request.params must be supplied in a query string. 
    1917    """ 
    2018     
  • trunk/cherrypy/_cpmodpy.py

    r1395 r1433  
    5959""" 
    6060 
     61import StringIO 
     62 
    6163import cherrypy 
    6264from cherrypy._cperror import format_exc, bare_error 
     
    104106 
    105107 
     108recursive = False 
     109 
    106110_isSetUp = False 
    107111def handler(req): 
     
    120124         
    121125        scheme = req.parsed_uri[0] or 'http' 
    122         request = cherrypy.engine.request(local, remote, scheme) 
    123126        req.get_basic_auth_pw() 
    124         request.login = req.user 
    125127         
    126128        try: 
     
    149151            else: 
    150152                raise ValueError(bad_value % "multiprocess") 
    151         request.multithread = bool(threaded) 
    152         request.multiprocess = bool(forked) 
    153153         
    154154        sn = cherrypy.tree.script_name(req.uri or "/") 
     
    156156            send_response(req, '404 Not Found', [], '') 
    157157        else: 
    158             request.app = cherrypy.tree.apps[sn] 
    159              
    160             # Run the CherryPy Request object and obtain the response 
     158            app = cherrypy.tree.apps[sn] 
     159            method = req.method 
     160            path = req.uri 
     161            qs = req.args or "" 
     162            sproto = req.protocol 
    161163            headers = req.headers_in.items() 
    162164            rfile = _ReadOnlyRequest(req) 
    163             response = request.run(req.method, req.uri, req.args or "", 
    164                                    req.protocol, headers, rfile) 
     165            prev = None 
     166             
     167            redirections = [] 
     168            while True: 
     169                request = cherrypy.engine.request(local, remote, scheme) 
     170                request.login = req.user 
     171                request.multithread = bool(threaded) 
     172                request.multiprocess = bool(forked) 
     173                request.app = app 
     174                request.prev = prev 
     175                 
     176                # Run the CherryPy Request object and obtain the response 
     177                try: 
     178                    response = request.run(method, path, qs, sproto, headers, rfile) 
     179                    break 
     180                except cherrypy.InternalRedirect, ir: 
     181                    request.close() 
     182                    prev = request 
     183                     
     184                    if not recursive: 
     185                        if ir.path in redirections: 
     186                            raise RuntimeError("InternalRedirector visited the " 
     187                                               "same URL twice: %r" % ir.path) 
     188                        else: 
     189                            # Add the *previous* path_info + qs to redirections. 
     190                            if qs: 
     191                                qs = "?" + qs 
     192                            redirections.append(sn + path + qs) 
     193                     
     194                    # Munge environment and try again. 
     195                    method = "GET" 
     196                    path = ir.path 
     197                    qs = ir.query_string 
     198                    rfile = StringIO.StringIO() 
    165199             
    166200            send_response(req, response.status, response.header_list, response.body) 
  • trunk/cherrypy/_cprequest.py

    r1424 r1433  
    136136    """An HTTP request.""" 
    137137     
     138    prev = None 
     139     
    138140    # Conversation/connection attributes 
    139141    local = http.Host("localhost", 80) 
     
    158160    methods_with_bodies = ("POST", "PUT") 
    159161    body = None 
    160     body_read = False 
    161     headers_read = False 
    162162     
    163163    # Dispatch attributes 
     
    169169    toolmaps = {} 
    170170    config = None 
    171     recursive_redirect = False 
    172171    is_index = None 
    173172     
     
    177176    error_page = {} 
    178177    show_tracebacks = True 
     178    throws = (KeyboardInterrupt, SystemExit, cherrypy.InternalRedirect) 
    179179    throw_errors = False 
    180180     
     
    199199         
    200200        self.closed = False 
    201         self.redirections = [] 
    202201         
    203202        # Put a *copy* of the class error_page into self. 
     
    277276            # app root (script_name) to the handler. 
    278277            self.script_name = self.app.script_name 
    279             self.path_info = path[len(self.script_name.rstrip("/")):] 
    280              
    281             # Loop to allow for InternalRedirect. 
    282             pi = self.path_info 
    283             qs = self.query_string 
    284             while True: 
    285                 try: 
    286                     self.respond(pi) 
    287                     break 
    288                 except cherrypy.InternalRedirect, ir: 
    289                     if (ir.path in self.redirections 
    290                         and not self.recursive_redirect): 
    291                         raise RuntimeError("InternalRedirect visited the " 
    292                                            "same URL twice: %s" % repr(ir.path)) 
    293                      
    294                     # Add the *previous* path_info + qs to self.redirections. 
    295                     if qs: 
    296                         qs = "?" + qs 
    297                     self.redirections.append(pi + qs) 
    298                      
    299                     pi = self.path_info = ir.path 
    300                     qs = self.query_string = ir.query_string 
    301                     if qs: 
    302                         self.params = http.parse_query_string(qs) 
    303         except (KeyboardInterrupt, SystemExit): 
     278            self.path_info = pi = path[len(self.script_name.rstrip("/")):] 
     279             
     280            self.respond(pi) 
     281             
     282        except self.throws: 
    304283            raise 
    305284        except: 
     
    307286                raise 
    308287            else: 
     288                # Failure in setup, error handler or finalize. Bypass them. 
    309289                # Can't use handle_error because we may not have hooks yet. 
    310290                cherrypy.log(traceback=True) 
    311                  
    312                 # Failure in setup, error handler or finalize. Bypass them. 
    313291                if self.show_tracebacks: 
    314292                    body = format_exc() 
     
    338316                        raise cherrypy.NotFound() 
    339317                     
    340                     if not self.headers_read: 
    341                         # Get the 'Host' header, so we can do HTTPRedirects properly. 
    342                         self.process_headers() 
     318                    # Get the 'Host' header, so we can do HTTPRedirects properly. 
     319                    self.process_headers() 
    343320                     
    344321                    # Make a copy of the class hooks 
     
    350327                    self.hooks.run('on_start_resource') 
    351328                     
    352                     if not self.body_read: 
    353                         if self.process_request_body: 
    354                             if self.method not in self.methods_with_bodies: 
    355                                 self.process_request_body = False 
     329                    if self.process_request_body: 
     330                        if self.method not in self.methods_with_bodies: 
     331                            self.process_request_body = False 
    356332                         
    357333                        if self.process_request_body: 
     
    376352            finally: 
    377353                self.hooks.run('on_end_resource') 
    378         except (KeyboardInterrupt, SystemExit, cherrypy.InternalRedirect)
     354        except self.throws
    379355            raise 
    380356        except: 
     
    384360     
    385361    def process_headers(self): 
    386         # Guard against re-reading body (e.g. on InternalRedirect) 
    387         if self.headers_read: 
    388             return 
    389         self.headers_read = True 
    390          
    391362        self.params = http.parse_query_string(self.query_string) 
    392363         
     
    452423    def process_body(self): 
    453424        """Convert request.rfile into request.params (or request.body).""" 
    454         # Guard against re-reading body (e.g. on InternalRedirect) 
    455         if self.body_read: 
    456             return 
    457         self.body_read = True 
    458          
    459425        # FieldStorage only recognizes POST, so fake it. 
    460426        methenv = {'REQUEST_METHOD': "POST"} 
  • trunk/cherrypy/_cpwsgi.py

    r1430 r1433  
    11"""WSGI interface (see PEP 333).""" 
    22 
     3import StringIO as _StringIO 
    34import sys as _sys 
    45 
     
    67from cherrypy import _cperror, _cpwsgiserver 
    78from cherrypy.lib import http as _http 
     9 
     10 
     11class InternalRedirector(object): 
     12    """WSGI middleware which handles cherrypy.InternalRedirect. 
     13     
     14    When cherrypy.InternalRedirect is raised, this middleware traps it, 
     15    rewrites the WSGI environ using the new path and query_string, 
     16    and calls the next application again. Because the wsgi.input stream 
     17    may have already been consumed by the next application, the redirected 
     18    call will always be of HTTP method "GET", and therefore any params must 
     19    be passed in the InternalRedirect object's query_string attribute. 
     20    If you need something more complicated, make and raise your own 
     21    exception and your own WSGI middlewre to trap it. ;) 
     22     
     23    It would be a bad idea to raise InternalRedirect after you've already 
     24    yielded response content, although an enterprising soul could choose 
     25    to abuse this. 
     26     
     27    nextapp: the next application callable in the WSGI chain. 
     28     
     29    recursive: if False (the default), each URL (path + qs) will be 
     30    stored, and, if the same URL is requested again, RuntimeError will 
     31    be raised. If 'recursive' is True, no such error will be raised. 
     32    """ 
     33     
     34    def __init__(self, nextapp, recursive=False): 
     35        self.nextapp = nextapp 
     36        self.recursive = recursive 
     37     
     38    def __call__(self, environ, start_response): 
     39        redirections = [] 
     40         
     41        env = environ.copy() 
     42        path = env.get('PATH_INFO', '') 
     43        qs = env.get('QUERY_STRING', '') 
     44         
     45        while True: 
     46            try: 
     47                for chunk in self.nextapp(env, start_response): 
     48                    yield chunk 
     49                break 
     50            except _cherrypy.InternalRedirect, ir: 
     51                if not self.recursive: 
     52                    if ir.path in redirections: 
     53                        raise RuntimeError("InternalRedirector visited the " 
     54                                           "same URL twice: %r" % ir.path) 
     55                    else: 
     56                        # Add the *previous* path_info + qs to redirections. 
     57                        if qs: 
     58                            qs = "?" + qs 
     59                        redirections.append(env.get('SCRIPT_NAME', '') + path + qs) 
     60                 
     61                # Munge environment and try again. 
     62                env['REQUEST_METHOD'] = "GET" 
     63                env['PATH_INFO'] = path = ir.path 
     64                env['QUERY_STRING'] = qs = ir.query_string 
     65                env['wsgi.input'] = _StringIO.StringIO() 
    866 
    967 
     
    2684    """ 
    2785     
    28     pipeline = [
     86    pipeline = [('iredir', InternalRedirector)
    2987    head = None 
    3088    config = {} 
    3189     
    32     throws = (KeyboardInterrupt, SystemExit
     90    throws = (KeyboardInterrupt, SystemExit, _cherrypy.InternalRedirect
    3391     
    3492    headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization', 
     
    77135        request.wsgi_environ = environ 
    78136        request.app = self.cpapp 
     137        request.prev = env('cherrypy.request') 
     138        environ['cherrypy.request'] = request 
    79139        return request 
    80140     
  • trunk/cherrypy/test/test_core.py

    r1415 r1433  
    165165         
    166166        def cousin(self, t): 
    167             return repr(cherrypy.request.redirections + 
    168                         [cherrypy.url(qs=cherrypy.request.query_string)]) 
     167            assert cherrypy.request.prev.closed 
     168            return cherrypy.request.prev.query_string 
    169169         
    170170        def petshop(self, user_id): 
     
    174174            elif user_id == "terrier": 
    175175                # Trade it for a fish when redirecting 
    176                 cherrypy.request.params = {"user_id": "fish"} 
    177                 raise cherrypy.InternalRedirect('/image/getImagesByUser') 
     176                raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=fish') 
    178177            else: 
    179178                # This should pass the user_id through to getImagesByUser 
    180                 raise cherrypy.InternalRedirect('/image/getImagesByUser'
     179                raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=%s' % user_id
    181180         
    182181        # We support Python 2.3, but the @-deco syntax would look like this: 
     
    195194        def custom_err(self): 
    196195            return "Something went horribly wrong." 
     196         
     197        def early_ir(self, arg): 
     198            return "whatever" 
     199        early_ir._cp_config = {'hooks.before_request_body': redir_custom} 
    197200     
    198201    class Image(Test): 
     
    585588        self.assertStatus(305) 
    586589         
    587         # InternalRedirect 
    588         self.getPage("/internalredirect/") 
    589         self.assertBody('hello') 
    590         self.assertStatus(200) 
    591          
    592         self.getPage("/internalredirect/petshop?user_id=Sir-not-appearing-in-this-film") 
    593         self.assertBody('0 images for Sir-not-appearing-in-this-film') 
    594         self.assertStatus(200) 
    595          
    596         self.getPage("/internalredirect/petshop?user_id=parrot") 
    597         self.assertBody('0 images for slug') 
    598         self.assertStatus(200) 
    599          
    600         self.getPage("/internalredirect/petshop?user_id=terrier") 
    601         self.assertBody('0 images for fish') 
    602         self.assertStatus(200) 
    603          
    604         self.getPage("/internalredirect/secure") 
    605         self.assertBody('Please log in') 
    606         self.assertStatus(200) 
    607          
    608         # Relative path in InternalRedirect. 
    609         # Also tests request.redirections 
    610         self.getPage("/internalredirect/relative?a=3&b=5") 
    611         self.assertBody("['/internalredirect/relative?a=3&b=5', " 
    612                         "'%s/internalredirect/cousin?t=6']" % self.base()) 
    613         self.assertStatus(200) 
    614          
    615         # InternalRedirect on error 
    616         self.getPage("/internalredirect/login/illegal/extra/vpath/atoms") 
    617         self.assertStatus(200) 
    618         self.assertBody("Something went horribly wrong.") 
    619          
    620590        # HTTPRedirect on error 
    621591        self.getPage("/redirect/error/") 
     
    631601            self.assertStatus(200) 
    632602            self.assertBody("(['%s/'], 303)" % self.base()) 
     603     
     604    def test_InternalRedirect(self): 
     605        # InternalRedirect 
     606        self.getPage("/internalredirect/") 
     607        self.assertBody('hello') 
     608        self.assertStatus(200) 
     609         
     610        # Test passthrough 
     611        self.getPage("/internalredirect/petshop?user_id=Sir-not-appearing-in-this-film") 
     612        self.assertBody('0 images for Sir-not-appearing-in-this-film') 
     613        self.assertStatus(200) 
     614         
     615        # Test args 
     616        self.getPage("/internalredirect/petshop?user_id=parrot") 
     617        self.assertBody('0 images for slug') 
     618        self.assertStatus(200) 
     619         
     620        # Test POST 
     621        self.getPage("/internalredirect/petshop", method="POST", 
     622                     body="user_id=terrier") 
     623        self.assertBody('0 images for fish') 
     624        self.assertStatus(200) 
     625         
     626        # Test ir before body read 
     627        self.getPage("/internalredirect/early_ir", method="POST", 
     628                     body="arg=aha!") 
     629        self.assertBody("Something went horribly wrong.") 
     630        self.assertStatus(200) 
     631         
     632        self.getPage("/internalredirect/secure") 
     633        self.assertBody('Please log in') 
     634        self.assertStatus(200) 
     635         
     636        # Relative path in InternalRedirect. 
     637        # Also tests request.prev. 
     638        self.getPage("/internalredirect/relative?a=3&b=5") 
     639        self.assertBody("a=3&b=5") 
     640        self.assertStatus(200) 
     641         
     642        # InternalRedirect on error 
     643        self.getPage("/internalredirect/login/illegal/extra/vpath/atoms") 
     644        self.assertStatus(200) 
     645        self.assertBody("Something went horribly wrong.") 
    633646     
    634647    def testFlatten(self): 

Hosted by WebFaction

Log in as guest/cpguest to create tickets