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
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
61
62
64
65 throws = (KeyboardInterrupt, SystemExit)
66 request = None
67
68 - def __init__(self, environ, start_response, cpapp, recursive=False):
75
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
106
107
108
109
110 _cherrypy.log(traceback=True, severity=40)
111 self.close()
112 raise
113
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
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
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
160
162 try:
163 chunk = self.iter_response.next()
164
165
166
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
190 self.iter_response = iter([])
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 self.close()
202 raise
203
204 return "".join(b)
205
207 """Close and de-reference the current request and response. (Core)"""
208 self.cpapp.release_serving()
209
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
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
236
237
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
253 """Translate CGI-environ header names to HTTP header names."""
254 for cgiName in environ:
255
256 if cgiName in self.headerNames:
257 yield self.headerNames[cgiName], environ[cgiName]
258 elif cgiName[:5] == "HTTP_":
259
260 translatedHeader = cgiName[5:].replace("_", "-")
261 yield translatedHeader, environ[cgiName]
262
263
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):
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):
314
316 """Config handler for the 'wsgi' namespace."""
317 if k == "pipeline":
318
319
320
321
322
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