Changeset 1433
- Timestamp:
- 11/16/06 18:29:52
- Files:
-
- trunk/cherrypy/_cperror.py (modified) (1 diff)
- trunk/cherrypy/_cpmodpy.py (modified) (5 diffs)
- trunk/cherrypy/_cprequest.py (modified) (12 diffs)
- trunk/cherrypy/_cpwsgi.py (modified) (4 diffs)
- trunk/cherrypy/test/test_core.py (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/cherrypy/_cperror.py
r1400 r1433 10 10 pass 11 11 12 12 13 class InternalRedirect(CherryPyException): 13 14 """Exception raised to switch to the handler for a different URL. 14 15 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. 19 17 """ 20 18 trunk/cherrypy/_cpmodpy.py
r1395 r1433 59 59 """ 60 60 61 import StringIO 62 61 63 import cherrypy 62 64 from cherrypy._cperror import format_exc, bare_error … … 104 106 105 107 108 recursive = False 109 106 110 _isSetUp = False 107 111 def handler(req): … … 120 124 121 125 scheme = req.parsed_uri[0] or 'http' 122 request = cherrypy.engine.request(local, remote, scheme)123 126 req.get_basic_auth_pw() 124 request.login = req.user125 127 126 128 try: … … 149 151 else: 150 152 raise ValueError(bad_value % "multiprocess") 151 request.multithread = bool(threaded)152 request.multiprocess = bool(forked)153 153 154 154 sn = cherrypy.tree.script_name(req.uri or "/") … … 156 156 send_response(req, '404 Not Found', [], '') 157 157 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 161 163 headers = req.headers_in.items() 162 164 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() 165 199 166 200 send_response(req, response.status, response.header_list, response.body) trunk/cherrypy/_cprequest.py
r1424 r1433 136 136 """An HTTP request.""" 137 137 138 prev = None 139 138 140 # Conversation/connection attributes 139 141 local = http.Host("localhost", 80) … … 158 160 methods_with_bodies = ("POST", "PUT") 159 161 body = None 160 body_read = False161 headers_read = False162 162 163 163 # Dispatch attributes … … 169 169 toolmaps = {} 170 170 config = None 171 recursive_redirect = False172 171 is_index = None 173 172 … … 177 176 error_page = {} 178 177 show_tracebacks = True 178 throws = (KeyboardInterrupt, SystemExit, cherrypy.InternalRedirect) 179 179 throw_errors = False 180 180 … … 199 199 200 200 self.closed = False 201 self.redirections = []202 201 203 202 # Put a *copy* of the class error_page into self. … … 277 276 # app root (script_name) to the handler. 278 277 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: 304 283 raise 305 284 except: … … 307 286 raise 308 287 else: 288 # Failure in setup, error handler or finalize. Bypass them. 309 289 # Can't use handle_error because we may not have hooks yet. 310 290 cherrypy.log(traceback=True) 311 312 # Failure in setup, error handler or finalize. Bypass them.313 291 if self.show_tracebacks: 314 292 body = format_exc() … … 338 316 raise cherrypy.NotFound() 339 317 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() 343 320 344 321 # Make a copy of the class hooks … … 350 327 self.hooks.run('on_start_resource') 351 328 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 356 332 357 333 if self.process_request_body: … … 376 352 finally: 377 353 self.hooks.run('on_end_resource') 378 except (KeyboardInterrupt, SystemExit, cherrypy.InternalRedirect):354 except self.throws: 379 355 raise 380 356 except: … … 384 360 385 361 def process_headers(self): 386 # Guard against re-reading body (e.g. on InternalRedirect)387 if self.headers_read:388 return389 self.headers_read = True390 391 362 self.params = http.parse_query_string(self.query_string) 392 363 … … 452 423 def process_body(self): 453 424 """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 return457 self.body_read = True458 459 425 # FieldStorage only recognizes POST, so fake it. 460 426 methenv = {'REQUEST_METHOD': "POST"} trunk/cherrypy/_cpwsgi.py
r1430 r1433 1 1 """WSGI interface (see PEP 333).""" 2 2 3 import StringIO as _StringIO 3 4 import sys as _sys 4 5 … … 6 7 from cherrypy import _cperror, _cpwsgiserver 7 8 from cherrypy.lib import http as _http 9 10 11 class 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() 8 66 9 67 … … 26 84 """ 27 85 28 pipeline = [ ]86 pipeline = [('iredir', InternalRedirector)] 29 87 head = None 30 88 config = {} 31 89 32 throws = (KeyboardInterrupt, SystemExit )90 throws = (KeyboardInterrupt, SystemExit, _cherrypy.InternalRedirect) 33 91 34 92 headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization', … … 77 135 request.wsgi_environ = environ 78 136 request.app = self.cpapp 137 request.prev = env('cherrypy.request') 138 environ['cherrypy.request'] = request 79 139 return request 80 140 trunk/cherrypy/test/test_core.py
r1415 r1433 165 165 166 166 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 169 169 170 170 def petshop(self, user_id): … … 174 174 elif user_id == "terrier": 175 175 # 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') 178 177 else: 179 178 # 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) 181 180 182 181 # We support Python 2.3, but the @-deco syntax would look like this: … … 195 194 def custom_err(self): 196 195 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} 197 200 198 201 class Image(Test): … … 585 588 self.assertStatus(305) 586 589 587 # InternalRedirect588 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.redirections610 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 error616 self.getPage("/internalredirect/login/illegal/extra/vpath/atoms")617 self.assertStatus(200)618 self.assertBody("Something went horribly wrong.")619 620 590 # HTTPRedirect on error 621 591 self.getPage("/redirect/error/") … … 631 601 self.assertStatus(200) 632 602 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.") 633 646 634 647 def testFlatten(self):

