1 """CherryPy Application and Tree objects."""
2
3 import os
4 import sys
5
6 import cherrypy
7 from cherrypy._cpcompat import ntou, py3k
8 from cherrypy import _cpconfig, _cplogging, _cprequest, _cpwsgi, tools
9 from cherrypy.lib import httputil
10
11
13 """A CherryPy Application.
14
15 Servers and gateways should not instantiate Request objects directly.
16 Instead, they should ask an Application object for a request object.
17
18 An instance of this class may also be used as a WSGI callable
19 (WSGI application object) for itself.
20 """
21
22 root = None
23 """The top-most container of page handlers for this app. Handlers should
24 be arranged in a hierarchy of attributes, matching the expected URI
25 hierarchy; the default dispatcher then searches this hierarchy for a
26 matching handler. When using a dispatcher other than the default,
27 this value may be None."""
28
29 config = {}
30 """A dict of {path: pathconf} pairs, where 'pathconf' is itself a dict
31 of {key: value} pairs."""
32
33 namespaces = _cpconfig.NamespaceSet()
34 toolboxes = {'tools': cherrypy.tools}
35
36 log = None
37 """A LogManager instance. See _cplogging."""
38
39 wsgiapp = None
40 """A CPWSGIApp instance. See _cpwsgi."""
41
42 request_class = _cprequest.Request
43 response_class = _cprequest.Response
44
45 relative_urls = False
46
47 - def __init__(self, root, script_name="", config=None):
60
62 return "%s.%s(%r, %r)" % (self.__module__, self.__class__.__name__,
63 self.root, self.script_name)
64
65 script_name_doc = """The URI "mount point" for this app. A mount point is that portion of
66 the URI which is constant for all URIs that are serviced by this
67 application; it does not include scheme, host, or proxy ("virtual host")
68 portions of the URI.
69
70 For example, if script_name is "/my/cool/app", then the URL
71 "http://www.example.com/my/cool/app/page1" might be handled by a
72 "page1" method on the root object.
73
74 The value of script_name MUST NOT end in a slash. If the script_name
75 refers to the root of the URI, it MUST be an empty string (not "/").
76
77 If script_name is explicitly set to None, then the script_name will be
78 provided for each call from request.wsgi_environ['SCRIPT_NAME'].
79 """
81 if self._script_name is None:
82
83 return cherrypy.serving.request.wsgi_environ['SCRIPT_NAME'].rstrip("/")
84 return self._script_name
86 if value:
87 value = value.rstrip("/")
88 self._script_name = value
89 script_name = property(fget=_get_script_name, fset=_set_script_name,
90 doc=script_name_doc)
91
98
100 """Return the most-specific value for key along path, or default."""
101 trail = path or "/"
102 while trail:
103 nodeconf = self.config.get(trail, {})
104
105 if key in nodeconf:
106 return nodeconf[key]
107
108 lastslash = trail.rfind("/")
109 if lastslash == -1:
110 break
111 elif lastslash == 0 and trail != "/":
112 trail = "/"
113 else:
114 trail = trail[:lastslash]
115
116 return default
117
132
145
146 - def __call__(self, environ, start_response):
148
149
151 """A registry of CherryPy applications, mounted at diverse points.
152
153 An instance of this class may also be used as a WSGI callable
154 (WSGI application object), in which case it dispatches to all
155 mounted apps.
156 """
157
158 apps = {}
159 """
160 A dict of the form {script name: application}, where "script name"
161 is a string declaring the URI mount point (no trailing slash), and
162 "application" is an instance of cherrypy.Application (or an arbitrary
163 WSGI callable if you happen to be using a WSGI server)."""
164
167
168 - def mount(self, root, script_name="", config=None):
169 """Mount a new app from a root object, script_name, and config.
170
171 root
172 An instance of a "controller class" (a collection of page
173 handler methods) which represents the root of the application.
174 This may also be an Application instance, or None if using
175 a dispatcher other than the default.
176
177 script_name
178 A string containing the "mount point" of the application.
179 This should start with a slash, and be the path portion of the
180 URL at which to mount the given root. For example, if root.index()
181 will handle requests to "http://www.example.com:8080/dept/app1/",
182 then the script_name argument would be "/dept/app1".
183
184 It MUST NOT end in a slash. If the script_name refers to the
185 root of the URI, it MUST be an empty string (not "/").
186
187 config
188 A file or dict containing application config.
189 """
190 if script_name is None:
191 raise TypeError(
192 "The 'script_name' argument may not be None. Application "
193 "objects may, however, possess a script_name of None (in "
194 "order to inpect the WSGI environ for SCRIPT_NAME upon each "
195 "request). You cannot mount such Applications on this Tree; "
196 "you must pass them to a WSGI server interface directly.")
197
198
199 script_name = script_name.rstrip("/")
200
201 if isinstance(root, Application):
202 app = root
203 if script_name != "" and script_name != app.script_name:
204 raise ValueError("Cannot specify a different script name and "
205 "pass an Application instance to cherrypy.mount")
206 script_name = app.script_name
207 else:
208 app = Application(root, script_name)
209
210
211 if (script_name == "" and root is not None
212 and not hasattr(root, "favicon_ico")):
213 favicon = os.path.join(os.getcwd(), os.path.dirname(__file__),
214 "favicon.ico")
215 root.favicon_ico = tools.staticfile.handler(favicon)
216
217 if config:
218 app.merge(config)
219
220 self.apps[script_name] = app
221
222 return app
223
224 - def graft(self, wsgi_callable, script_name=""):
229
231 """The script_name of the app at the given path, or None.
232
233 If path is None, cherrypy.request is used.
234 """
235 if path is None:
236 try:
237 request = cherrypy.serving.request
238 path = httputil.urljoin(request.script_name,
239 request.path_info)
240 except AttributeError:
241 return None
242
243 while True:
244 if path in self.apps:
245 return path
246
247 if path == "":
248 return None
249
250
251 path = path[:path.rfind("/")]
252
253 - def __call__(self, environ, start_response):
254
255
256
257 env1x = environ
258 if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
259 env1x = _cpwsgi.downgrade_wsgi_ux_to_1x(environ)
260 path = httputil.urljoin(env1x.get('SCRIPT_NAME', ''),
261 env1x.get('PATH_INFO', ''))
262 sn = self.script_name(path or "/")
263 if sn is None:
264 start_response('404 Not Found', [])
265 return []
266
267 app = self.apps[sn]
268
269
270 environ = environ.copy()
271 if not py3k:
272 if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
273
274 enc = environ[ntou('wsgi.url_encoding')]
275 environ[ntou('SCRIPT_NAME')] = sn.decode(enc)
276 environ[ntou('PATH_INFO')] = path[len(sn.rstrip("/")):].decode(enc)
277 else:
278
279 environ['SCRIPT_NAME'] = sn
280 environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
281 else:
282 if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
283
284 environ['SCRIPT_NAME'] = sn
285 environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
286 else:
287
288 environ['SCRIPT_NAME'] = sn.encode('utf-8').decode('ISO-8859-1')
289 environ['PATH_INFO'] = path[len(sn.rstrip("/")):].encode('utf-8').decode('ISO-8859-1')
290 return app(environ, start_response)
291