1 """WSGI interface (see PEP 333 and 3333).
2
3 Note that WSGI environ keys and values are 'native strings'; that is,
4 whatever the type of "" is. For Python 2, that's a byte string; for Python 3,
5 it's a unicode string. But PEP 3333 says: "even if Python's str type is
6 actually Unicode "under the hood", the content of native strings must
7 still be translatable to bytes via the Latin-1 encoding!"
8 """
9
10 import sys as _sys
11
12 import cherrypy as _cherrypy
13 from cherrypy._cpcompat import BytesIO, bytestr, ntob, ntou, py3k, unicodestr
14 from cherrypy import _cperror
15 from cherrypy.lib import httputil
16 from cherrypy.lib import is_closable_iterator
17
19 """Return a new environ dict for WSGI 1.x from the given WSGI u.x environ.
20 """
21 env1x = {}
22
23 url_encoding = environ[ntou('wsgi.url_encoding')]
24 for k, v in list(environ.items()):
25 if k in [ntou('PATH_INFO'), ntou('SCRIPT_NAME'), ntou('QUERY_STRING')]:
26 v = v.encode(url_encoding)
27 elif isinstance(v, unicodestr):
28 v = v.encode('ISO-8859-1')
29 env1x[k.encode('ISO-8859-1')] = v
30
31 return env1x
32
33
35
36 """Select a different WSGI application based on the Host header.
37
38 This can be useful when running multiple sites within one CP server.
39 It allows several domains to point to different applications. For example::
40
41 root = Root()
42 RootApp = cherrypy.Application(root)
43 Domain2App = cherrypy.Application(root)
44 SecureApp = cherrypy.Application(Secure())
45
46 vhost = cherrypy._cpwsgi.VirtualHost(RootApp,
47 domains={'www.domain2.example': Domain2App,
48 'www.domain2.example:443': SecureApp,
49 })
50
51 cherrypy.tree.graft(vhost)
52 """
53 default = None
54 """Required. The default WSGI application."""
55
56 use_x_forwarded_host = True
57 """If True (the default), any "X-Forwarded-Host"
58 request header will be used instead of the "Host" header. This
59 is commonly added by HTTP servers (such as Apache) when proxying."""
60
61 domains = {}
62 """A dict of {host header value: application} pairs.
63 The incoming "Host" request header is looked up in this dict,
64 and, if a match is found, the corresponding WSGI application
65 will be called instead of the default. Note that you often need
66 separate entries for "example.com" and "www.example.com".
67 In addition, "Host" headers may contain the port number.
68 """
69
70 - def __init__(self, default, domains=None, use_x_forwarded_host=True):
74
75 - def __call__(self, environ, start_response):
84
85
87
88 """WSGI middleware that handles raised cherrypy.InternalRedirect."""
89
90 - def __init__(self, nextapp, recursive=False):
93
94 - def __call__(self, environ, start_response):
95 redirections = []
96 while True:
97 environ = environ.copy()
98 try:
99 return self.nextapp(environ, start_response)
100 except _cherrypy.InternalRedirect:
101 ir = _sys.exc_info()[1]
102 sn = environ.get('SCRIPT_NAME', '')
103 path = environ.get('PATH_INFO', '')
104 qs = environ.get('QUERY_STRING', '')
105
106
107 old_uri = sn + path
108 if qs:
109 old_uri += "?" + qs
110 redirections.append(old_uri)
111
112 if not self.recursive:
113
114
115 new_uri = sn + ir.path
116 if ir.query_string:
117 new_uri += "?" + ir.query_string
118 if new_uri in redirections:
119 ir.request.close()
120 raise RuntimeError("InternalRedirector visited the "
121 "same URL twice: %r" % new_uri)
122
123
124 environ['REQUEST_METHOD'] = "GET"
125 environ['PATH_INFO'] = ir.path
126 environ['QUERY_STRING'] = ir.query_string
127 environ['wsgi.input'] = BytesIO()
128 environ['CONTENT_LENGTH'] = "0"
129 environ['cherrypy.previous_request'] = ir.request
130
131
133
134 """WSGI middleware that traps exceptions."""
135
136 - def __init__(self, nextapp, throws=(KeyboardInterrupt, SystemExit)):
139
140 - def __call__(self, environ, start_response):
147
148
150
151 response = iter([])
152
153 - def __init__(self, nextapp, environ, start_response, throws):
162
164 self.started_response = True
165 return self
166
167 if py3k:
169 return self.trap(next, self.iter_response)
170 else:
172 return self.trap(self.iter_response.next)
173
177
178 - def trap(self, func, *args, **kwargs):
179 try:
180 return func(*args, **kwargs)
181 except self.throws:
182 raise
183 except StopIteration:
184 raise
185 except:
186 tb = _cperror.format_exc()
187
188 _cherrypy.log(tb, severity=40)
189 if not _cherrypy.request.show_tracebacks:
190 tb = ""
191 s, h, b = _cperror.bare_error(tb)
192 if py3k:
193
194 s = s.decode('ISO-8859-1')
195 h = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
196 for k, v in h]
197 if self.started_response:
198
199 self.iter_response = iter([])
200 else:
201 self.iter_response = iter(b)
202
203 try:
204 self.start_response(s, h, _sys.exc_info())
205 except:
206
207
208
209
210
211 _cherrypy.log(traceback=True, severity=40)
212 raise
213
214 if self.started_response:
215 return ntob("").join(b)
216 else:
217 return b
218
219
220
221
222
224
225 """WSGI response iterable for CherryPy applications."""
226
227 - def __init__(self, environ, start_response, cpapp):
228 self.cpapp = cpapp
229 try:
230 if not py3k:
231 if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
232 environ = downgrade_wsgi_ux_to_1x(environ)
233 self.environ = environ
234 self.run()
235
236 r = _cherrypy.serving.response
237
238 outstatus = r.output_status
239 if not isinstance(outstatus, bytestr):
240 raise TypeError("response.output_status is not a byte string.")
241
242 outheaders = []
243 for k, v in r.header_list:
244 if not isinstance(k, bytestr):
245 raise TypeError(
246 "response.header_list key %r is not a byte string." %
247 k)
248 if not isinstance(v, bytestr):
249 raise TypeError(
250 "response.header_list value %r is not a byte string." %
251 v)
252 outheaders.append((k, v))
253
254 if py3k:
255
256
257
258
259 outstatus = outstatus.decode('ISO-8859-1')
260 outheaders = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
261 for k, v in outheaders]
262
263 self.iter_response = iter(r.body)
264 self.write = start_response(outstatus, outheaders)
265 except:
266 self.close()
267 raise
268
271
272 if py3k:
274 return next(self.iter_response)
275 else:
277 return self.iter_response.next()
278
280 """Close and de-reference the current request and response. (Core)"""
281 streaming = _cherrypy.serving.response.stream
282 self.cpapp.release_serving()
283
284
285
286
287
288 if streaming and is_closable_iterator(self.iter_response):
289 iter_close = self.iter_response.close
290 try:
291 iter_close()
292 except Exception:
293 _cherrypy.log(traceback=True, severity=40)
294
296 """Create a Request object using environ."""
297 env = self.environ.get
298
299 local = httputil.Host('', int(env('SERVER_PORT', 80)),
300 env('SERVER_NAME', ''))
301 remote = httputil.Host(env('REMOTE_ADDR', ''),
302 int(env('REMOTE_PORT', -1) or -1),
303 env('REMOTE_HOST', ''))
304 scheme = env('wsgi.url_scheme')
305 sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1")
306 request, resp = self.cpapp.get_serving(local, remote, scheme, sproto)
307
308
309
310
311 request.login = env('LOGON_USER') or env('REMOTE_USER') or None
312 request.multithread = self.environ['wsgi.multithread']
313 request.multiprocess = self.environ['wsgi.multiprocess']
314 request.wsgi_environ = self.environ
315 request.prev = env('cherrypy.previous_request', None)
316
317 meth = self.environ['REQUEST_METHOD']
318
319 path = httputil.urljoin(self.environ.get('SCRIPT_NAME', ''),
320 self.environ.get('PATH_INFO', ''))
321 qs = self.environ.get('QUERY_STRING', '')
322
323 if py3k:
324
325
326
327 old_enc = self.environ.get('wsgi.url_encoding', 'ISO-8859-1')
328 new_enc = self.cpapp.find_config(self.environ.get('PATH_INFO', ''),
329 "request.uri_encoding", 'utf-8')
330 if new_enc.lower() != old_enc.lower():
331
332
333
334
335 try:
336 u_path = path.encode(old_enc).decode(new_enc)
337 u_qs = qs.encode(old_enc).decode(new_enc)
338 except (UnicodeEncodeError, UnicodeDecodeError):
339
340 pass
341 else:
342
343 path = u_path
344 qs = u_qs
345
346 rproto = self.environ.get('SERVER_PROTOCOL')
347 headers = self.translate_headers(self.environ)
348 rfile = self.environ['wsgi.input']
349 request.run(meth, path, qs, rproto, headers, rfile)
350
351 headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization',
352 'CONTENT_LENGTH': 'Content-Length',
353 'CONTENT_TYPE': 'Content-Type',
354 'REMOTE_HOST': 'Remote-Host',
355 'REMOTE_ADDR': 'Remote-Addr',
356 }
357
359 """Translate CGI-environ header names to HTTP header names."""
360 for cgiName in environ:
361
362 if cgiName in self.headerNames:
363 yield self.headerNames[cgiName], environ[cgiName]
364 elif cgiName[:5] == "HTTP_":
365
366 translatedHeader = cgiName[5:].replace("_", "-")
367 yield translatedHeader, environ[cgiName]
368
369
371
372 """A WSGI application object for a CherryPy Application."""
373
374 pipeline = [('ExceptionTrapper', ExceptionTrapper),
375 ('InternalRedirector', InternalRedirector),
376 ]
377 """A list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a
378 constructor that takes an initial, positional 'nextapp' argument,
379 plus optional keyword arguments, and returns a WSGI application
380 (that takes environ and start_response arguments). The 'name' can
381 be any you choose, and will correspond to keys in self.config."""
382
383 head = None
384 """Rather than nest all apps in the pipeline on each call, it's only
385 done the first time, and the result is memoized into self.head. Set
386 this to None again if you change self.pipeline after calling self."""
387
388 config = {}
389 """A dict whose keys match names listed in the pipeline. Each
390 value is a further dict which will be passed to the corresponding
391 named WSGI callable (from the pipeline) as keyword arguments."""
392
393 response_class = AppResponse
394 """The class to instantiate and return as the next app in the WSGI chain.
395 """
396
397 - def __init__(self, cpapp, pipeline=None):
403
404 - def tail(self, environ, start_response):
405 """WSGI application callable for the actual CherryPy application.
406
407 You probably shouldn't call this; call self.__call__ instead,
408 so that any WSGI middleware in self.pipeline can run first.
409 """
410 return self.response_class(environ, start_response, self.cpapp)
411
412 - def __call__(self, environ, start_response):
423
425 """Config handler for the 'wsgi' namespace."""
426 if k == "pipeline":
427
428
429
430
431
432 self.pipeline.extend(v)
433 elif k == "response_class":
434 self.response_class = v
435 else:
436 name, arg = k.split(".", 1)
437 bucket = self.config.setdefault(name, {})
438 bucket[arg] = v
439