Fixed python_path problem.
[smonitor.git] / lib / cherrypy / _cpwsgi.py
blobaa4b7631aa7013e5db74c99c7fae17d48684f688
1 """WSGI interface (see PEP 333 and 3333).
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 """
10 import sys as _sys
12 import cherrypy as _cherrypy
13 from cherrypy._cpcompat import BytesIO
14 from cherrypy import _cperror
15 from cherrypy.lib import httputil
18 def downgrade_wsgi_ux_to_1x(environ):
19 """Return a new environ dict for WSGI 1.x from the given WSGI u.x environ."""
20 env1x = {}
22 url_encoding = environ[u'wsgi.url_encoding']
23 for k, v in environ.items():
24 if k in [u'PATH_INFO', u'SCRIPT_NAME', u'QUERY_STRING']:
25 v = v.encode(url_encoding)
26 elif isinstance(v, unicode):
27 v = v.encode('ISO-8859-1')
28 env1x[k.encode('ISO-8859-1')] = v
30 return env1x
33 class VirtualHost(object):
34 """Select a different WSGI application based on the Host header.
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::
39 root = Root()
40 RootApp = cherrypy.Application(root)
41 Domain2App = cherrypy.Application(root)
42 SecureApp = cherrypy.Application(Secure())
44 vhost = cherrypy._cpwsgi.VirtualHost(RootApp,
45 domains={'www.domain2.example': Domain2App,
46 'www.domain2.example:443': SecureApp,
49 cherrypy.tree.graft(vhost)
50 """
51 default = None
52 """Required. The default WSGI application."""
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."""
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 """
68 def __init__(self, default, domains=None, use_x_forwarded_host=True):
69 self.default = default
70 self.domains = domains or {}
71 self.use_x_forwarded_host = use_x_forwarded_host
73 def __call__(self, environ, start_response):
74 domain = environ.get('HTTP_HOST', '')
75 if self.use_x_forwarded_host:
76 domain = environ.get("HTTP_X_FORWARDED_HOST", domain)
78 nextapp = self.domains.get(domain)
79 if nextapp is None:
80 nextapp = self.default
81 return nextapp(environ, start_response)
84 class InternalRedirector(object):
85 """WSGI middleware that handles raised cherrypy.InternalRedirect."""
87 def __init__(self, nextapp, recursive=False):
88 self.nextapp = nextapp
89 self.recursive = recursive
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, ir:
98 sn = environ.get('SCRIPT_NAME', '')
99 path = environ.get('PATH_INFO', '')
100 qs = environ.get('QUERY_STRING', '')
102 # Add the *previous* path_info + qs to redirections.
103 old_uri = sn + path
104 if qs:
105 old_uri += "?" + qs
106 redirections.append(old_uri)
108 if not self.recursive:
109 # Check to see if the new URI has been redirected to already
110 new_uri = sn + ir.path
111 if ir.query_string:
112 new_uri += "?" + ir.query_string
113 if new_uri in redirections:
114 ir.request.close()
115 raise RuntimeError("InternalRedirector visited the "
116 "same URL twice: %r" % new_uri)
118 # Munge the environment and try again.
119 environ['REQUEST_METHOD'] = "GET"
120 environ['PATH_INFO'] = ir.path
121 environ['QUERY_STRING'] = ir.query_string
122 environ['wsgi.input'] = BytesIO()
123 environ['CONTENT_LENGTH'] = "0"
124 environ['cherrypy.previous_request'] = ir.request
127 class ExceptionTrapper(object):
128 """WSGI middleware that traps exceptions."""
130 def __init__(self, nextapp, throws=(KeyboardInterrupt, SystemExit)):
131 self.nextapp = nextapp
132 self.throws = throws
134 def __call__(self, environ, start_response):
135 return _TrappedResponse(self.nextapp, environ, start_response, self.throws)
138 class _TrappedResponse(object):
140 response = iter([])
142 def __init__(self, nextapp, environ, start_response, throws):
143 self.nextapp = nextapp
144 self.environ = environ
145 self.start_response = start_response
146 self.throws = throws
147 self.started_response = False
148 self.response = self.trap(self.nextapp, self.environ, self.start_response)
149 self.iter_response = iter(self.response)
151 def __iter__(self):
152 self.started_response = True
153 return self
155 def next(self):
156 return self.trap(self.iter_response.next)
158 def close(self):
159 if hasattr(self.response, 'close'):
160 self.response.close()
162 def trap(self, func, *args, **kwargs):
163 try:
164 return func(*args, **kwargs)
165 except self.throws:
166 raise
167 except StopIteration:
168 raise
169 except:
170 tb = _cperror.format_exc()
171 #print('trapped (started %s):' % self.started_response, tb)
172 _cherrypy.log(tb, severity=40)
173 if not _cherrypy.request.show_tracebacks:
174 tb = ""
175 s, h, b = _cperror.bare_error(tb)
176 if self.started_response:
177 # Empty our iterable (so future calls raise StopIteration)
178 self.iter_response = iter([])
179 else:
180 self.iter_response = iter(b)
182 try:
183 self.start_response(s, h, _sys.exc_info())
184 except:
185 # "The application must not trap any exceptions raised by
186 # start_response, if it called start_response with exc_info.
187 # Instead, it should allow such exceptions to propagate
188 # back to the server or gateway."
189 # But we still log and call close() to clean up ourselves.
190 _cherrypy.log(traceback=True, severity=40)
191 raise
193 if self.started_response:
194 return "".join(b)
195 else:
196 return b
199 # WSGI-to-CP Adapter #
202 class AppResponse(object):
203 """WSGI response iterable for CherryPy applications."""
205 def __init__(self, environ, start_response, cpapp):
206 if environ.get(u'wsgi.version') == (u'u', 0):
207 environ = downgrade_wsgi_ux_to_1x(environ)
208 self.environ = environ
209 self.cpapp = cpapp
210 try:
211 self.run()
212 except:
213 self.close()
214 raise
215 r = _cherrypy.serving.response
216 self.iter_response = iter(r.body)
217 self.write = start_response(r.output_status, r.header_list)
219 def __iter__(self):
220 return self
222 def next(self):
223 return self.iter_response.next()
225 def close(self):
226 """Close and de-reference the current request and response. (Core)"""
227 self.cpapp.release_serving()
229 def run(self):
230 """Create a Request object using environ."""
231 env = self.environ.get
233 local = httputil.Host('', int(env('SERVER_PORT', 80)),
234 env('SERVER_NAME', ''))
235 remote = httputil.Host(env('REMOTE_ADDR', ''),
236 int(env('REMOTE_PORT', -1) or -1),
237 env('REMOTE_HOST', ''))
238 scheme = env('wsgi.url_scheme')
239 sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1")
240 request, resp = self.cpapp.get_serving(local, remote, scheme, sproto)
242 # LOGON_USER is served by IIS, and is the name of the
243 # user after having been mapped to a local account.
244 # Both IIS and Apache set REMOTE_USER, when possible.
245 request.login = env('LOGON_USER') or env('REMOTE_USER') or None
246 request.multithread = self.environ['wsgi.multithread']
247 request.multiprocess = self.environ['wsgi.multiprocess']
248 request.wsgi_environ = self.environ
249 request.prev = env('cherrypy.previous_request', None)
251 meth = self.environ['REQUEST_METHOD']
253 path = httputil.urljoin(self.environ.get('SCRIPT_NAME', ''),
254 self.environ.get('PATH_INFO', ''))
255 qs = self.environ.get('QUERY_STRING', '')
256 rproto = self.environ.get('SERVER_PROTOCOL')
257 headers = self.translate_headers(self.environ)
258 rfile = self.environ['wsgi.input']
259 request.run(meth, path, qs, rproto, headers, rfile)
261 headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization',
262 'CONTENT_LENGTH': 'Content-Length',
263 'CONTENT_TYPE': 'Content-Type',
264 'REMOTE_HOST': 'Remote-Host',
265 'REMOTE_ADDR': 'Remote-Addr',
268 def translate_headers(self, environ):
269 """Translate CGI-environ header names to HTTP header names."""
270 for cgiName in environ:
271 # We assume all incoming header keys are uppercase already.
272 if cgiName in self.headerNames:
273 yield self.headerNames[cgiName], environ[cgiName]
274 elif cgiName[:5] == "HTTP_":
275 # Hackish attempt at recovering original header names.
276 translatedHeader = cgiName[5:].replace("_", "-")
277 yield translatedHeader, environ[cgiName]
280 class CPWSGIApp(object):
281 """A WSGI application object for a CherryPy Application."""
283 pipeline = [('ExceptionTrapper', ExceptionTrapper),
284 ('InternalRedirector', InternalRedirector),
286 """A list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a
287 constructor that takes an initial, positional 'nextapp' argument,
288 plus optional keyword arguments, and returns a WSGI application
289 (that takes environ and start_response arguments). The 'name' can
290 be any you choose, and will correspond to keys in self.config."""
292 head = None
293 """Rather than nest all apps in the pipeline on each call, it's only
294 done the first time, and the result is memoized into self.head. Set
295 this to None again if you change self.pipeline after calling self."""
297 config = {}
298 """A dict whose keys match names listed in the pipeline. Each
299 value is a further dict which will be passed to the corresponding
300 named WSGI callable (from the pipeline) as keyword arguments."""
302 response_class = AppResponse
303 """The class to instantiate and return as the next app in the WSGI chain."""
305 def __init__(self, cpapp, pipeline=None):
306 self.cpapp = cpapp
307 self.pipeline = self.pipeline[:]
308 if pipeline:
309 self.pipeline.extend(pipeline)
310 self.config = self.config.copy()
312 def tail(self, environ, start_response):
313 """WSGI application callable for the actual CherryPy application.
315 You probably shouldn't call this; call self.__call__ instead,
316 so that any WSGI middleware in self.pipeline can run first.
318 return self.response_class(environ, start_response, self.cpapp)
320 def __call__(self, environ, start_response):
321 head = self.head
322 if head is None:
323 # Create and nest the WSGI apps in our pipeline (in reverse order).
324 # Then memoize the result in self.head.
325 head = self.tail
326 for name, callable in self.pipeline[::-1]:
327 conf = self.config.get(name, {})
328 head = callable(head, **conf)
329 self.head = head
330 return head(environ, start_response)
332 def namespace_handler(self, k, v):
333 """Config handler for the 'wsgi' namespace."""
334 if k == "pipeline":
335 # Note this allows multiple 'wsgi.pipeline' config entries
336 # (but each entry will be processed in a 'random' order).
337 # It should also allow developers to set default middleware
338 # in code (passed to self.__init__) that deployers can add to
339 # (but not remove) via config.
340 self.pipeline.extend(v)
341 elif k == "response_class":
342 self.response_class = v
343 else:
344 name, arg = k.split(".", 1)
345 bucket = self.config.setdefault(name, {})
346 bucket[arg] = v