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