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
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 self.cpapp.release_serving()
282
284 """Create a Request object using environ."""
285 env = self.environ.get
286
287 local = httputil.Host('', int(env('SERVER_PORT', 80)),
288 env('SERVER_NAME', ''))
289 remote = httputil.Host(env('REMOTE_ADDR', ''),
290 int(env('REMOTE_PORT', -1) or -1),
291 env('REMOTE_HOST', ''))
292 scheme = env('wsgi.url_scheme')
293 sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1")
294 request, resp = self.cpapp.get_serving(local, remote, scheme, sproto)
295
296
297
298
299 request.login = env('LOGON_USER') or env('REMOTE_USER') or None
300 request.multithread = self.environ['wsgi.multithread']
301 request.multiprocess = self.environ['wsgi.multiprocess']
302 request.wsgi_environ = self.environ
303 request.prev = env('cherrypy.previous_request', None)
304
305 meth = self.environ['REQUEST_METHOD']
306
307 path = httputil.urljoin(self.environ.get('SCRIPT_NAME', ''),
308 self.environ.get('PATH_INFO', ''))
309 qs = self.environ.get('QUERY_STRING', '')
310
311 if py3k:
312
313
314
315 old_enc = self.environ.get('wsgi.url_encoding', 'ISO-8859-1')
316 new_enc = self.cpapp.find_config(self.environ.get('PATH_INFO', ''),
317 "request.uri_encoding", 'utf-8')
318 if new_enc.lower() != old_enc.lower():
319
320
321
322
323 try:
324 u_path = path.encode(old_enc).decode(new_enc)
325 u_qs = qs.encode(old_enc).decode(new_enc)
326 except (UnicodeEncodeError, UnicodeDecodeError):
327
328 pass
329 else:
330
331 path = u_path
332 qs = u_qs
333
334 rproto = self.environ.get('SERVER_PROTOCOL')
335 headers = self.translate_headers(self.environ)
336 rfile = self.environ['wsgi.input']
337 request.run(meth, path, qs, rproto, headers, rfile)
338
339 headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization',
340 'CONTENT_LENGTH': 'Content-Length',
341 'CONTENT_TYPE': 'Content-Type',
342 'REMOTE_HOST': 'Remote-Host',
343 'REMOTE_ADDR': 'Remote-Addr',
344 }
345
347 """Translate CGI-environ header names to HTTP header names."""
348 for cgiName in environ:
349
350 if cgiName in self.headerNames:
351 yield self.headerNames[cgiName], environ[cgiName]
352 elif cgiName[:5] == "HTTP_":
353
354 translatedHeader = cgiName[5:].replace("_", "-")
355 yield translatedHeader, environ[cgiName]
356
357
359
360 """A WSGI application object for a CherryPy Application."""
361
362 pipeline = [('ExceptionTrapper', ExceptionTrapper),
363 ('InternalRedirector', InternalRedirector),
364 ]
365 """A list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a
366 constructor that takes an initial, positional 'nextapp' argument,
367 plus optional keyword arguments, and returns a WSGI application
368 (that takes environ and start_response arguments). The 'name' can
369 be any you choose, and will correspond to keys in self.config."""
370
371 head = None
372 """Rather than nest all apps in the pipeline on each call, it's only
373 done the first time, and the result is memoized into self.head. Set
374 this to None again if you change self.pipeline after calling self."""
375
376 config = {}
377 """A dict whose keys match names listed in the pipeline. Each
378 value is a further dict which will be passed to the corresponding
379 named WSGI callable (from the pipeline) as keyword arguments."""
380
381 response_class = AppResponse
382 """The class to instantiate and return as the next app in the WSGI chain.
383 """
384
385 - def __init__(self, cpapp, pipeline=None):
391
392 - def tail(self, environ, start_response):
393 """WSGI application callable for the actual CherryPy application.
394
395 You probably shouldn't call this; call self.__call__ instead,
396 so that any WSGI middleware in self.pipeline can run first.
397 """
398 return self.response_class(environ, start_response, self.cpapp)
399
400 - def __call__(self, environ, start_response):
411
413 """Config handler for the 'wsgi' namespace."""
414 if k == "pipeline":
415
416
417
418
419
420 self.pipeline.extend(v)
421 elif k == "response_class":
422 self.response_class = v
423 else:
424 name, arg = k.split(".", 1)
425 bucket = self.config.setdefault(name, {})
426 bucket[arg] = v
427