| 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() |
|---|
| 66 | | |
|---|
| 67 | | |
|---|
| 68 | | class CPWSGIApp(object): |
|---|
| 69 | | """A WSGI application object for a CherryPy Application. |
|---|
| 70 | | |
|---|
| 71 | | pipeline: a list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a |
|---|
| 72 | | constructor that takes an initial, positional 'nextapp' argument, |
|---|
| 73 | | plus optional keyword arguments, and returns a WSGI application |
|---|
| 74 | | (that takes environ and start_response arguments). The 'name' can |
|---|
| 75 | | be any you choose, and will correspond to keys in self.config. |
|---|
| 76 | | |
|---|
| 77 | | head: rather than nest all apps in the pipeline on each call, it's only |
|---|
| 78 | | done the first time, and the result is memoized into self.head. Set |
|---|
| 79 | | this to None again if you change self.pipeline after calling self. |
|---|
| 80 | | |
|---|
| 81 | | config: a dict whose keys match names listed in the pipeline. Each |
|---|
| 82 | | value is a further dict which will be passed to the corresponding |
|---|
| 83 | | named WSGI callable (from the pipeline) as keyword arguments. |
|---|
| 84 | | """ |
|---|
| 85 | | |
|---|
| 86 | | pipeline = [('iredir', InternalRedirector)] |
|---|
| 87 | | head = None |
|---|
| 88 | | config = {} |
|---|
| | 62 | self.setenv(ir) |
|---|
| | 63 | |
|---|
| | 64 | def setenv(self, ir): |
|---|
| | 65 | env = self.environ |
|---|
| | 66 | if not self.recursive: |
|---|
| | 67 | if ir.path in self.redirections: |
|---|
| | 68 | raise RuntimeError("InternalRedirector visited the " |
|---|
| | 69 | "same URL twice: %r" % ir.path) |
|---|
| | 70 | else: |
|---|
| | 71 | # Add the *previous* path_info + qs to redirections. |
|---|
| | 72 | sn = env.get('SCRIPT_NAME', '') |
|---|
| | 73 | path = env.get('PATH_INFO', '') |
|---|
| | 74 | qs = env.get('QUERY_STRING', '') |
|---|
| | 75 | if qs: |
|---|
| | 76 | qs = "?" + qs |
|---|
| | 77 | self.redirections.append(sn + path + qs) |
|---|
| | 78 | |
|---|
| | 79 | # Munge environment and try again. |
|---|
| | 80 | env['REQUEST_METHOD'] = "GET" |
|---|
| | 81 | env['PATH_INFO'] = ir.path |
|---|
| | 82 | env['QUERY_STRING'] = ir.query_string |
|---|
| | 83 | env['wsgi.input'] = _StringIO.StringIO() |
|---|
| | 84 | |
|---|
| | 85 | def close(self): |
|---|
| | 86 | if hasattr(self.response, "close"): |
|---|
| | 87 | self.response.close() |
|---|
| | 88 | |
|---|
| | 89 | def __iter__(self): |
|---|
| | 90 | return self |
|---|
| | 91 | |
|---|
| | 92 | def next(self): |
|---|
| | 93 | while True: |
|---|
| | 94 | try: |
|---|
| | 95 | return self.iter_response.next() |
|---|
| | 96 | except _cherrypy.InternalRedirect, ir: |
|---|
| | 97 | self.setenv(ir) |
|---|
| | 98 | self.setapp() |
|---|
| | 99 | |
|---|
| | 100 | |
|---|
| | 101 | |
|---|
| | 102 | # WSGI-to-CP Adapter # |
|---|
| | 103 | |
|---|
| | 104 | |
|---|
| | 105 | class AppResponse(object): |
|---|
| | 108 | request = None |
|---|
| | 109 | |
|---|
| | 110 | def __init__(self, environ, start_response, cpapp): |
|---|
| | 111 | try: |
|---|
| | 112 | self.request = self.get_engine_request(environ, cpapp) |
|---|
| | 113 | |
|---|
| | 114 | meth = environ['REQUEST_METHOD'] |
|---|
| | 115 | path = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '') |
|---|
| | 116 | qs = environ.get('QUERY_STRING', '') |
|---|
| | 117 | rproto = environ.get('SERVER_PROTOCOL') |
|---|
| | 118 | headers = self.translate_headers(environ) |
|---|
| | 119 | rfile = environ['wsgi.input'] |
|---|
| | 120 | |
|---|
| | 121 | response = self.request.run(meth, path, qs, rproto, headers, rfile) |
|---|
| | 122 | s, h, b = response.status, response.header_list, response.body |
|---|
| | 123 | exc = None |
|---|
| | 124 | except self.throws: |
|---|
| | 125 | self.close() |
|---|
| | 126 | raise |
|---|
| | 127 | except: |
|---|
| | 128 | if getattr(self.request, "throw_errors", False): |
|---|
| | 129 | self.close() |
|---|
| | 130 | raise |
|---|
| | 131 | |
|---|
| | 132 | tb = _cperror.format_exc() |
|---|
| | 133 | _cherrypy.log(tb) |
|---|
| | 134 | if not getattr(self.request, "show_tracebacks", True): |
|---|
| | 135 | tb = "" |
|---|
| | 136 | s, h, b = _cperror.bare_error(tb) |
|---|
| | 137 | exc = _sys.exc_info() |
|---|
| | 138 | |
|---|
| | 139 | self.iter_response = iter(b) |
|---|
| | 140 | |
|---|
| | 141 | try: |
|---|
| | 142 | start_response(s, h, exc) |
|---|
| | 143 | except self.throws: |
|---|
| | 144 | self.close() |
|---|
| | 145 | raise |
|---|
| | 146 | except: |
|---|
| | 147 | if getattr(self.request, "throw_errors", False): |
|---|
| | 148 | self.close() |
|---|
| | 149 | raise |
|---|
| | 150 | |
|---|
| | 151 | _cherrypy.log(traceback=True) |
|---|
| | 152 | self.close() |
|---|
| | 153 | |
|---|
| | 154 | # CherryPy test suite expects bare_error body to be output, |
|---|
| | 155 | # so don't call start_response (which, according to PEP 333, |
|---|
| | 156 | # may raise its own error at that point). |
|---|
| | 157 | s, h, b = _cperror.bare_error() |
|---|
| | 158 | self.iter_response = iter(b) |
|---|
| | 159 | |
|---|
| | 160 | def __iter__(self): |
|---|
| | 161 | return self |
|---|
| | 162 | |
|---|
| | 163 | def next(self): |
|---|
| | 164 | try: |
|---|
| | 165 | chunk = self.iter_response.next() |
|---|
| | 166 | # WSGI requires all data to be of type "str". This coercion should |
|---|
| | 167 | # not take any time at all if chunk is already of type "str". |
|---|
| | 168 | # If it's unicode, it could be a big performance hit (x ~500). |
|---|
| | 169 | if not isinstance(chunk, str): |
|---|
| | 170 | chunk = chunk.encode("ISO-8859-1") |
|---|
| | 171 | return chunk |
|---|
| | 172 | except self.throws: |
|---|
| | 173 | raise |
|---|
| | 174 | except StopIteration: |
|---|
| | 175 | raise |
|---|
| | 176 | except: |
|---|
| | 177 | if getattr(self.request, "throw_errors", False): |
|---|
| | 178 | raise |
|---|
| | 179 | |
|---|
| | 180 | _cherrypy.log(traceback=True) |
|---|
| | 181 | |
|---|
| | 182 | # CherryPy test suite expects bare_error body to be output, |
|---|
| | 183 | # so don't call start_response (which, according to PEP 333, |
|---|
| | 184 | # may raise its own error at that point). |
|---|
| | 185 | s, h, b = _cperror.bare_error() |
|---|
| | 186 | self.iter_response = iter([]) |
|---|
| | 187 | return "".join(b) |
|---|
| | 188 | |
|---|
| | 189 | def close(self): |
|---|
| | 190 | if hasattr(self.request, "close"): |
|---|
| | 191 | try: |
|---|
| | 192 | self.request.close() |
|---|
| | 193 | except: |
|---|
| | 194 | _cherrypy.log(traceback=True) |
|---|
| | 195 | |
|---|
| | 196 | def get_engine_request(self, environ, cpapp): |
|---|
| | 197 | """Return a Request object from the CherryPy Engine using environ.""" |
|---|
| | 198 | env = environ.get |
|---|
| | 199 | |
|---|
| | 200 | local = _http.Host('', int(env('SERVER_PORT', 80)), |
|---|
| | 201 | env('SERVER_NAME', '')) |
|---|
| | 202 | remote = _http.Host(env('REMOTE_ADDR', ''), |
|---|
| | 203 | int(env('REMOTE_PORT', -1)), |
|---|
| | 204 | env('REMOTE_HOST', '')) |
|---|
| | 205 | scheme = env('wsgi.url_scheme') |
|---|
| | 206 | sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1") |
|---|
| | 207 | request = _cherrypy.engine.request(local, remote, scheme, sproto) |
|---|
| | 208 | |
|---|
| | 209 | # LOGON_USER is served by IIS, and is the name of the |
|---|
| | 210 | # user after having been mapped to a local account. |
|---|
| | 211 | # Both IIS and Apache set REMOTE_USER, when possible. |
|---|
| | 212 | request.login = env('LOGON_USER') or env('REMOTE_USER') or None |
|---|
| | 213 | request.multithread = environ['wsgi.multithread'] |
|---|
| | 214 | request.multiprocess = environ['wsgi.multiprocess'] |
|---|
| | 215 | request.wsgi_environ = environ |
|---|
| | 216 | request.app = cpapp |
|---|
| | 217 | request.prev = env('cherrypy.request') |
|---|
| | 218 | environ['cherrypy.request'] = request |
|---|
| | 219 | return request |
|---|
| | 238 | |
|---|
| | 239 | |
|---|
| | 240 | class CPWSGIApp(object): |
|---|
| | 241 | """A WSGI application object for a CherryPy Application. |
|---|
| | 242 | |
|---|
| | 243 | pipeline: a list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a |
|---|
| | 244 | constructor that takes an initial, positional 'nextapp' argument, |
|---|
| | 245 | plus optional keyword arguments, and returns a WSGI application |
|---|
| | 246 | (that takes environ and start_response arguments). The 'name' can |
|---|
| | 247 | be any you choose, and will correspond to keys in self.config. |
|---|
| | 248 | |
|---|
| | 249 | head: rather than nest all apps in the pipeline on each call, it's only |
|---|
| | 250 | done the first time, and the result is memoized into self.head. Set |
|---|
| | 251 | this to None again if you change self.pipeline after calling self. |
|---|
| | 252 | |
|---|
| | 253 | config: a dict whose keys match names listed in the pipeline. Each |
|---|
| | 254 | value is a further dict which will be passed to the corresponding |
|---|
| | 255 | named WSGI callable (from the pipeline) as keyword arguments. |
|---|
| | 256 | """ |
|---|
| | 257 | |
|---|
| | 258 | pipeline = [('iredir', InternalRedirector)] |
|---|
| | 259 | head = None |
|---|
| | 260 | config = {} |
|---|
| 117 | | def get_request(self, environ): |
|---|
| 118 | | env = environ.get |
|---|
| 119 | | |
|---|
| 120 | | local = _http.Host('', int(env('SERVER_PORT', 80)), |
|---|
| 121 | | env('SERVER_NAME', '')) |
|---|
| 122 | | remote = _http.Host(env('REMOTE_ADDR', ''), |
|---|
| 123 | | int(env('REMOTE_PORT', -1)), |
|---|
| 124 | | env('REMOTE_HOST', '')) |
|---|
| 125 | | scheme = env('wsgi.url_scheme') |
|---|
| 126 | | sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1") |
|---|
| 127 | | request = _cherrypy.engine.request(local, remote, scheme, sproto) |
|---|
| 128 | | |
|---|
| 129 | | # LOGON_USER is served by IIS, and is the name of the |
|---|
| 130 | | # user after having been mapped to a local account. |
|---|
| 131 | | # Both IIS and Apache set REMOTE_USER, when possible. |
|---|
| 132 | | request.login = env('LOGON_USER') or env('REMOTE_USER') or None |
|---|
| 133 | | request.multithread = environ['wsgi.multithread'] |
|---|
| 134 | | request.multiprocess = environ['wsgi.multiprocess'] |
|---|
| 135 | | request.wsgi_environ = environ |
|---|
| 136 | | request.app = self.cpapp |
|---|
| 137 | | request.prev = env('cherrypy.request') |
|---|
| 138 | | environ['cherrypy.request'] = request |
|---|
| 139 | | return request |
|---|
| | 269 | response_class = AppResponse |
|---|
| 147 | | request = None |
|---|
| 148 | | try: |
|---|
| 149 | | request = self.get_request(environ) |
|---|
| 150 | | |
|---|
| 151 | | meth = environ['REQUEST_METHOD'] |
|---|
| 152 | | path = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '') |
|---|
| 153 | | qs = environ.get('QUERY_STRING', '') |
|---|
| 154 | | rproto = environ.get('SERVER_PROTOCOL') |
|---|
| 155 | | headers = self.translate_headers(environ) |
|---|
| 156 | | rfile = environ['wsgi.input'] |
|---|
| 157 | | |
|---|
| 158 | | response = request.run(meth, path, qs, rproto, headers, rfile) |
|---|
| 159 | | s, h, b = response.status, response.header_list, response.body |
|---|
| 160 | | exc = None |
|---|
| 161 | | except self.throws, ex: |
|---|
| 162 | | self._close_req(request) |
|---|
| 163 | | raise ex |
|---|
| 164 | | except: |
|---|
| 165 | | if request and request.throw_errors: |
|---|
| 166 | | raise |
|---|
| 167 | | |
|---|
| 168 | | tb = _cperror.format_exc() |
|---|
| 169 | | _cherrypy.log(tb) |
|---|
| 170 | | if request and not request.show_tracebacks: |
|---|
| 171 | | tb = "" |
|---|
| 172 | | s, h, b = _cperror.bare_error(tb) |
|---|
| 173 | | |
|---|
| 174 | | exc = _sys.exc_info() |
|---|
| 175 | | |
|---|
| 176 | | try: |
|---|
| 177 | | start_response(s, h, exc) |
|---|
| 178 | | for chunk in b: |
|---|
| 179 | | # WSGI requires all data to be of type "str". This coercion should |
|---|
| 180 | | # not take any time at all if chunk is already of type "str". |
|---|
| 181 | | # If it's unicode, it could be a big performance hit (x ~500). |
|---|
| 182 | | if not isinstance(chunk, str): |
|---|
| 183 | | chunk = chunk.encode("ISO-8859-1") |
|---|
| 184 | | yield chunk |
|---|
| 185 | | self._close_req(request) |
|---|
| 186 | | except self.throws, ex: |
|---|
| 187 | | self._close_req(request) |
|---|
| 188 | | raise ex |
|---|
| 189 | | except: |
|---|
| 190 | | _cherrypy.log(traceback=True) |
|---|
| 191 | | self._close_req(request) |
|---|
| 192 | | |
|---|
| 193 | | # CherryPy test suite expects bare_error body to be output, |
|---|
| 194 | | # so don't call start_response (which, according to PEP 333, |
|---|
| 195 | | # may raise its own error at that point). |
|---|
| 196 | | s, h, b = _cperror.bare_error() |
|---|
| 197 | | for chunk in b: |
|---|
| 198 | | if not isinstance(chunk, str): |
|---|
| 199 | | chunk = chunk.encode("ISO-8859-1") |
|---|
| 200 | | yield chunk |
|---|
| 201 | | |
|---|
| 202 | | def _close_req(self, request): |
|---|
| 203 | | if hasattr(request, "close"): |
|---|
| 204 | | try: |
|---|
| 205 | | request.close() |
|---|
| 206 | | except: |
|---|
| 207 | | _cherrypy.log(traceback=True) |
|---|
| | 277 | return self.response_class(environ, start_response, self.cpapp) |
|---|