Package cherrypy :: Module _cpwsgi
[hide private]
[frames] | no frames]

Source Code for Module cherrypy._cpwsgi

  1  """WSGI interface (see PEP 333).""" 
  2   
  3  import StringIO as _StringIO 
  4  import sys as _sys 
  5   
  6  import cherrypy as _cherrypy 
  7  from cherrypy import _cperror, wsgiserver 
  8  from cherrypy.lib import http as _http 
  9   
 10   
 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. 35 """ 36
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):
48 self.redirections = [] 49 self.recursive = recursive 50 self.environ = environ.copy() 51 self.nextapp = nextapp 52 self.start_response = start_response 53 self.response = None 54 self.iter_response = None 55 self.setapp()
56
57 - 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 128 exc = None 129 except self.throws: 130 self.close() 131 raise 132 except: 133 if getattr(self.request, "throw_errors", False): 134 self.close() 135 raise 136 137 tb = _cperror.format_exc() 138 _cherrypy.log(tb) 139 if not getattr(self.request, "show_tracebacks", True): 140 tb = "" 141 s, h, b = _cperror.bare_error(tb) 142 exc = _sys.exc_info() 143 144 self.iter_response = iter(b) 145 146 try: 147 start_response(s, h, exc) 148 except self.throws: 149 self.close() 150 raise 151 except: 152 if getattr(self.request, "throw_errors", False): 153 self.close() 154 raise 155 156 _cherrypy.log(traceback=True) 157 self.close() 158 159 # CherryPy test suite expects bare_error body to be output, 160 # so don't call start_response (which, according to PEP 333, 161 # may raise its own error at that point). 162 s, h, b = _cperror.bare_error() 163 self.iter_response = iter(b)
164
165 - def __iter__(self):
166 return self
167
168 - def next(self):
169 try: 170 chunk = self.iter_response.next() 171 # WSGI requires all data to be of type "str". This coercion should 172 # not take any time at all if chunk is already of type "str". 173 # If it's unicode, it could be a big performance hit (x ~500). 174 if not isinstance(chunk, str): 175 chunk = chunk.encode("ISO-8859-1") 176 return chunk 177 except self.throws: 178 raise 179 except StopIteration: 180 raise 181 except: 182 if getattr(self.request, "throw_errors", False): 183 raise 184 185 _cherrypy.log(traceback=True) 186 187 # CherryPy test suite expects bare_error body to be output, 188 # so don't call start_response (which, according to PEP 333, 189 # may raise its own error at that point). 190 s, h, b = _cperror.bare_error() 191 self.iter_response = iter([]) 192 return "".join(b)
193
194 - def close(self):
195 _cherrypy.engine.release()
196
197 - def get_engine_request(self, environ, cpapp):
198 """Return a Request object from the CherryPy Engine using environ.""" 199 env = environ.get 200 201 local = _http.Host('', int(env('SERVER_PORT', 80)), 202 env('SERVER_NAME', '')) 203 remote = _http.Host(env('REMOTE_ADDR', ''), 204 int(env('REMOTE_PORT', -1)), 205 env('REMOTE_HOST', '')) 206 scheme = env('wsgi.url_scheme') 207 sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1") 208 request = _cherrypy.engine.request(local, remote, scheme, sproto) 209 210 # LOGON_USER is served by IIS, and is the name of the 211 # user after having been mapped to a local account. 212 # Both IIS and Apache set REMOTE_USER, when possible. 213 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 220 return request
221 222 headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization', 223 'CONTENT_LENGTH': 'Content-Length', 224 'CONTENT_TYPE': 'Content-Type', 225 'REMOTE_HOST': 'Remote-Host', 226 'REMOTE_ADDR': 'Remote-Addr', 227 } 228
229 - def translate_headers(self, environ):
230 """Translate CGI-environ header names to HTTP header names.""" 231 for cgiName in environ: 232 # We assume all incoming header keys are uppercase already. 233 if cgiName in self.headerNames: 234 yield self.headerNames[cgiName], environ[cgiName] 235 elif cgiName[:5] == "HTTP_": 236 # Hackish attempt at recovering original header names. 237 translatedHeader = cgiName[5:].replace("_", "-") 238 yield translatedHeader, environ[cgiName]
239 240
241 -class CPWSGIApp(object):
242 """A WSGI application object for a CherryPy Application. 243 244 pipeline: a list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a 245 constructor that takes an initial, positional 'nextapp' argument, 246 plus optional keyword arguments, and returns a WSGI application 247 (that takes environ and start_response arguments). The 'name' can 248 be any you choose, and will correspond to keys in self.config. 249 250 head: rather than nest all apps in the pipeline on each call, it's only 251 done the first time, and the result is memoized into self.head. Set 252 this to None again if you change self.pipeline after calling self. 253 254 config: a dict whose keys match names listed in the pipeline. Each 255 value is a further dict which will be passed to the corresponding 256 named WSGI callable (from the pipeline) as keyword arguments. 257 """ 258 259 pipeline = [('iredir', InternalRedirector)] 260 head = None 261 config = {} 262 263 response_class = AppResponse 264
265 - def __init__(self, cpapp, pipeline=None):
266 self.cpapp = cpapp 267 self.pipeline = self.pipeline[:] 268 if pipeline: 269 self.pipeline.extend(pipeline) 270 self.config = self.config.copy()
271
272 - def tail(self, environ, start_response):
273 """WSGI application callable for the actual CherryPy application. 274 275 You probably shouldn't call this; call self.__call__ instead, 276 so that any WSGI middleware in self.pipeline can run first. 277 """ 278 return self.response_class(environ, start_response, self.cpapp)
279
280 - def __call__(self, environ, start_response):
281 head = self.head 282 if head is None: 283 # Create and nest the WSGI apps in our pipeline (in reverse order). 284 # Then memoize the result in self.head. 285 head = self.tail 286 for name, callable in self.pipeline[::-1]: 287 conf = self.config.get(name, {}) 288 head = callable(head, **conf) 289 self.head = head 290 return head(environ, start_response)
291
292 - def namespace_handler(self, k, v):
293 """Config handler for the 'wsgi' namespace.""" 294 if k == "pipeline": 295 # Note this allows multiple 'wsgi.pipeline' config entries 296 # (but each entry will be processed in a 'random' order). 297 # It should also allow developers to set default middleware 298 # in code (passed to self.__init__) that deployers can add to 299 # (but not remove) via config. 300 self.pipeline.extend(v) 301 else: 302 name, arg = k.split(".", 1) 303 bucket = self.config.setdefault(name, {}) 304 bucket[arg] = v
305 306 307 308 # Server components # 309 310
311 -class CPHTTPRequest(wsgiserver.HTTPRequest):
312
313 - def parse_request(self):
314 mhs = _cherrypy.server.max_request_header_size 315 if mhs > 0: 316 self.rfile = _http.SizeCheckWrapper(self.rfile, mhs) 317 318 try: 319 wsgiserver.HTTPRequest.parse_request(self) 320 except _http.MaxSizeExceeded: 321 self.simple_response("413 Request Entity Too Large") 322 _cherrypy.log(traceback=True)
323
324 - def decode_chunked(self):
325 """Decode the 'chunked' transfer coding.""" 326 if isinstance(self.rfile, _http.SizeCheckWrapper): 327 self.rfile = self.rfile.rfile 328 mbs = _cherrypy.server.max_request_body_size 329 if mbs > 0: 330 self.rfile = _http.SizeCheckWrapper(self.rfile, mbs) 331 try: 332 return wsgiserver.HTTPRequest.decode_chunked(self) 333 except _http.MaxSizeExceeded: 334 self.simple_response("413 Request Entity Too Large") 335 _cherrypy.log(traceback=True) 336 return False
337 338
339 -class CPHTTPConnection(wsgiserver.HTTPConnection):
340 341 RequestHandlerClass = CPHTTPRequest
342 343
344 -class CPWSGIServer(wsgiserver.CherryPyWSGIServer):
345 346 """Wrapper for wsgiserver.CherryPyWSGIServer. 347 348 wsgiserver has been designed to not reference CherryPy in any way, 349 so that it can be used in other frameworks and applications. Therefore, 350 we wrap it here, so we can set our own mount points from cherrypy.tree. 351 352 """ 353 354 ConnectionClass = CPHTTPConnection 355
356 - def __init__(self):
357 server = _cherrypy.server 358 sockFile = server.socket_file 359 if sockFile: 360 bind_addr = sockFile 361 else: 362 bind_addr = (server.socket_host, server.socket_port) 363 364 s = wsgiserver.CherryPyWSGIServer 365 # We could just pass cherrypy.tree, but by passing tree.apps, 366 # we get correct SCRIPT_NAMEs as early as possible. 367 s.__init__(self, bind_addr, _cherrypy.tree.apps.items(), 368 server.thread_pool, 369 server.socket_host, 370 request_queue_size = server.socket_queue_size, 371 timeout = server.socket_timeout, 372 ) 373 self.protocol = server.protocol_version 374 self.ssl_certificate = server.ssl_certificate 375 self.ssl_private_key = server.ssl_private_key
376