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 
  8  from cherrypy.lib import http as _http 
  9   
 10   
11 -class VirtualHost(object):
12 """Select a different WSGI application based on the Host header. 13 14 This can be useful when running multiple sites within one CP server. 15 It allows several domains to point to different applications. For example: 16 17 root = Root() 18 RootApp = cherrypy.Application(root) 19 Domain2App = cherrypy.Application(root) 20 SecureApp = cherrypy.Application(Secure()) 21 22 vhost = cherrypy._cpwsgi.VirtualHost(RootApp, 23 domains={'www.domain2.example': Domain2App, 24 'www.domain2.example:443': SecureApp, 25 }) 26 27 cherrypy.tree.graft(vhost) 28 29 default: required. The default WSGI application. 30 31 use_x_forwarded_host: if True (the default), any "X-Forwarded-Host" 32 request header will be used instead of the "Host" header. This 33 is commonly added by HTTP servers (such as Apache) when proxying. 34 35 domains: a dict of {host header value: application} pairs. 36 The incoming "Host" request header is looked up in this dict, 37 and, if a match is found, the corresponding WSGI application 38 will be called instead of the default. Note that you often need 39 separate entries for "example.com" and "www.example.com". 40 In addition, "Host" headers may contain the port number. 41 """ 42
43 - def __init__(self, default, domains=None, use_x_forwarded_host=True):
44 self.default = default 45 self.domains = domains or {} 46 self.use_x_forwarded_host = use_x_forwarded_host
47
48 - def __call__(self, environ, start_response):
49 domain = environ.get('HTTP_HOST', '') 50 if self.use_x_forwarded_host: 51 domain = environ.get("HTTP_X_FORWARDED_HOST", domain) 52 53 nextapp = self.domains.get(domain) 54 if nextapp is None: 55 nextapp = self.default 56 return nextapp(environ, start_response)
57 58 59 60 # WSGI-to-CP Adapter # 61 62
63 -class AppResponse(object):
64 65 throws = (KeyboardInterrupt, SystemExit) 66 request = None 67
68 - def __init__(self, environ, start_response, cpapp, recursive=False):
69 self.redirections = [] 70 self.recursive = recursive 71 self.environ = environ 72 self.start_response = start_response 73 self.cpapp = cpapp 74 self.setapp()
75
76 - def setapp(self):
77 try: 78 self.request = self.get_request() 79 s, h, b = self.get_response() 80 self.iter_response = iter(b) 81 self.start_response(s, h) 82 except self.throws: 83 self.close() 84 raise 85 except _cherrypy.InternalRedirect, ir: 86 self.environ['cherrypy.previous_request'] = _cherrypy.serving.request 87 self.close() 88 self.iredirect(ir.path, ir.query_string) 89 return 90 except: 91 if getattr(self.request, "throw_errors", False): 92 self.close() 93 raise 94 95 tb = _cperror.format_exc() 96 _cherrypy.log(tb, severity=40) 97 if not getattr(self.request, "show_tracebacks", True): 98 tb = "" 99 s, h, b = _cperror.bare_error(tb) 100 self.iter_response = iter(b) 101 102 try: 103 self.start_response(s, h, _sys.exc_info()) 104 except: 105 # "The application must not trap any exceptions raised by 106 # start_response, if it called start_response with exc_info. 107 # Instead, it should allow such exceptions to propagate 108 # back to the server or gateway." 109 # But we still log and call close() to clean up ourselves. 110 _cherrypy.log(traceback=True, severity=40) 111 self.close() 112 raise
113
114 - def iredirect(self, path, query_string):
115 """Doctor self.environ and perform an internal redirect. 116 117 When cherrypy.InternalRedirect is raised, this method is called. 118 It rewrites the WSGI environ using the new path and query_string, 119 and calls a new CherryPy Request object. Because the wsgi.input 120 stream may have already been consumed by the next application, 121 the redirected call will always be of HTTP method "GET"; therefore, 122 any params must be passed in the query_string argument, which is 123 formed from InternalRedirect.query_string when using that exception. 124 If you need something more complicated, make and raise your own 125 exception and write your own AppResponse subclass to trap it. ;) 126 127 It would be a bad idea to redirect after you've already yielded 128 response content, although an enterprising soul could choose 129 to abuse this. 130 """ 131 env = self.environ 132 if not self.recursive: 133 sn = env.get('SCRIPT_NAME', '') 134 qs = query_string 135 if qs: 136 qs = "?" + qs 137 if sn + path + qs in self.redirections: 138 raise RuntimeError("InternalRedirector visited the " 139 "same URL twice: %r + %r + %r" % 140 (sn, path, qs)) 141 else: 142 # Add the *previous* path_info + qs to redirections. 143 p = env.get('PATH_INFO', '') 144 qs = env.get('QUERY_STRING', '') 145 if qs: 146 qs = "?" + qs 147 self.redirections.append(sn + p + qs) 148 149 # Munge environment and try again. 150 env['REQUEST_METHOD'] = "GET" 151 env['PATH_INFO'] = path 152 env['QUERY_STRING'] = query_string 153 env['wsgi.input'] = _StringIO.StringIO() 154 env['CONTENT_LENGTH'] = "0" 155 156 self.setapp()
157
158 - def __iter__(self):
159 return self
160
161 - def next(self):
162 try: 163 chunk = self.iter_response.next() 164 # WSGI requires all data to be of type "str". This coercion should 165 # not take any time at all if chunk is already of type "str". 166 # If it's unicode, it could be a big performance hit (x ~500). 167 if not isinstance(chunk, str): 168 chunk = chunk.encode("ISO-8859-1") 169 return chunk 170 except self.throws: 171 self.close() 172 raise 173 except _cherrypy.InternalRedirect, ir: 174 self.environ['cherrypy.previous_request'] = _cherrypy.serving.request 175 self.close() 176 self.iredirect(ir.path, ir.query_string) 177 except StopIteration: 178 raise 179 except: 180 if getattr(self.request, "throw_errors", False): 181 self.close() 182 raise 183 184 tb = _cperror.format_exc() 185 _cherrypy.log(tb, severity=40) 186 if not getattr(self.request, "show_tracebacks", True): 187 tb = "" 188 s, h, b = _cperror.bare_error(tb) 189 # Empty our iterable (so future calls raise StopIteration) 190 self.iter_response = iter([]) 191 192 try: 193 self.start_response(s, h, _sys.exc_info()) 194 except: 195 # "The application must not trap any exceptions raised by 196 # start_response, if it called start_response with exc_info. 197 # Instead, it should allow such exceptions to propagate 198 # back to the server or gateway." 199 # But we still log and call close() to clean up ourselves. 200 _cherrypy.log(traceback=True, severity=40) 201 self.close() 202 raise 203 204 return "".join(b)
205
206 - def close(self):
207 """Close and de-reference the current request and response. (Core)""" 208 self.cpapp.release_serving()
209
210 - def get_response(self):
211 """Run self.request and return its response.""" 212 meth = self.environ['REQUEST_METHOD'] 213 path = _http.urljoin(self.environ.get('SCRIPT_NAME', ''), 214 self.environ.get('PATH_INFO', '')) 215 qs = self.environ.get('QUERY_STRING', '') 216 rproto = self.environ.get('SERVER_PROTOCOL') 217 headers = self.translate_headers(self.environ) 218 rfile = self.environ['wsgi.input'] 219 response = self.request.run(meth, path, qs, rproto, headers, rfile) 220 return response.status, response.header_list, response.body
221
222 - def get_request(self):
223 """Create a Request object using environ.""" 224 env = self.environ.get 225 226 local = _http.Host('', int(env('SERVER_PORT', 80)), 227 env('SERVER_NAME', '')) 228 remote = _http.Host(env('REMOTE_ADDR', ''), 229 int(env('REMOTE_PORT', -1)), 230 env('REMOTE_HOST', '')) 231 scheme = env('wsgi.url_scheme') 232 sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1") 233 request, resp = self.cpapp.get_serving(local, remote, scheme, sproto) 234 235 # LOGON_USER is served by IIS, and is the name of the 236 # user after having been mapped to a local account. 237 # Both IIS and Apache set REMOTE_USER, when possible. 238 request.login = env('LOGON_USER') or env('REMOTE_USER') or None 239 request.multithread = self.environ['wsgi.multithread'] 240 request.multiprocess = self.environ['wsgi.multiprocess'] 241 request.wsgi_environ = self.environ 242 request.prev = env('cherrypy.previous_request', None) 243 return request
244 245 headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization', 246 'CONTENT_LENGTH': 'Content-Length', 247 'CONTENT_TYPE': 'Content-Type', 248 'REMOTE_HOST': 'Remote-Host', 249 'REMOTE_ADDR': 'Remote-Addr', 250 } 251
252 - def translate_headers(self, environ):
253 """Translate CGI-environ header names to HTTP header names.""" 254 for cgiName in environ: 255 # We assume all incoming header keys are uppercase already. 256 if cgiName in self.headerNames: 257 yield self.headerNames[cgiName], environ[cgiName] 258 elif cgiName[:5] == "HTTP_": 259 # Hackish attempt at recovering original header names. 260 translatedHeader = cgiName[5:].replace("_", "-") 261 yield translatedHeader, environ[cgiName]
262 263
264 -class CPWSGIApp(object):
265 """A WSGI application object for a CherryPy Application. 266 267 pipeline: a list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a 268 constructor that takes an initial, positional 'nextapp' argument, 269 plus optional keyword arguments, and returns a WSGI application 270 (that takes environ and start_response arguments). The 'name' can 271 be any you choose, and will correspond to keys in self.config. 272 273 head: rather than nest all apps in the pipeline on each call, it's only 274 done the first time, and the result is memoized into self.head. Set 275 this to None again if you change self.pipeline after calling self. 276 277 config: a dict whose keys match names listed in the pipeline. Each 278 value is a further dict which will be passed to the corresponding 279 named WSGI callable (from the pipeline) as keyword arguments. 280 """ 281 282 pipeline = [] 283 head = None 284 config = {} 285 286 response_class = AppResponse 287
288 - def __init__(self, cpapp, pipeline=None):
289 self.cpapp = cpapp 290 self.pipeline = self.pipeline[:] 291 if pipeline: 292 self.pipeline.extend(pipeline) 293 self.config = self.config.copy()
294
295 - def tail(self, environ, start_response):
296 """WSGI application callable for the actual CherryPy application. 297 298 You probably shouldn't call this; call self.__call__ instead, 299 so that any WSGI middleware in self.pipeline can run first. 300 """ 301 return self.response_class(environ, start_response, self.cpapp)
302
303 - def __call__(self, environ, start_response):
304 head = self.head 305 if head is None: 306 # Create and nest the WSGI apps in our pipeline (in reverse order). 307 # Then memoize the result in self.head. 308 head = self.tail 309 for name, callable in self.pipeline[::-1]: 310 conf = self.config.get(name, {}) 311 head = callable(head, **conf) 312 self.head = head 313 return head(environ, start_response)
314
315 - def namespace_handler(self, k, v):
316 """Config handler for the 'wsgi' namespace.""" 317 if k == "pipeline": 318 # Note this allows multiple 'wsgi.pipeline' config entries 319 # (but each entry will be processed in a 'random' order). 320 # It should also allow developers to set default middleware 321 # in code (passed to self.__init__) that deployers can add to 322 # (but not remove) via config. 323 self.pipeline.extend(v) 324 elif k == "response_class": 325 self.response_class = v 326 else: 327 name, arg = k.split(".", 1) 328 bucket = self.config.setdefault(name, {}) 329 bucket[arg] = v
330