| 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 | |
|---|
| | 14 | class 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. |
|---|
| 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): |
|---|
| 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() |
|---|
| | 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() |
|---|
| 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): |
|---|