1 """CherryPy Application and Tree objects."""
2
3 import os
4
5 import cherrypy
6 from cherrypy._cpcompat import ntou, py3k
7 from cherrypy import _cpconfig, _cplogging, _cprequest, _cpwsgi, tools
8 from cherrypy.lib import httputil
9
10
12
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
66 is that portion of the URI which is constant for all URIs that are
67 serviced by this application; it does not include scheme, host, or proxy
68 ("virtual host") 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 """
80
82 if self._script_name is not None:
83 return self._script_name
84
85
86
87 return cherrypy.serving.request.wsgi_environ['SCRIPT_NAME'].rstrip("/")
88
90 if value:
91 value = value.rstrip("/")
92 self._script_name = value
93 script_name = property(fget=_get_script_name, fset=_set_script_name,
94 doc=script_name_doc)
95
102
104 """Return the most-specific value for key along path, or default."""
105 trail = path or "/"
106 while trail:
107 nodeconf = self.config.get(trail, {})
108
109 if key in nodeconf:
110 return nodeconf[key]
111
112 lastslash = trail.rfind("/")
113 if lastslash == -1:
114 break
115 elif lastslash == 0 and trail != "/":
116 trail = "/"
117 else:
118 trail = trail[:lastslash]
119
120 return default
121
136
149
150 - def __call__(self, environ, start_response):
152
153
155
156 """A registry of CherryPy applications, mounted at diverse points.
157
158 An instance of this class may also be used as a WSGI callable
159 (WSGI application object), in which case it dispatches to all
160 mounted apps.
161 """
162
163 apps = {}
164 """
165 A dict of the form {script name: application}, where "script name"
166 is a string declaring the URI mount point (no trailing slash), and
167 "application" is an instance of cherrypy.Application (or an arbitrary
168 WSGI callable if you happen to be using a WSGI server)."""
169
172
173 - def mount(self, root, script_name="", config=None):
174 """Mount a new app from a root object, script_name, and config.
175
176 root
177 An instance of a "controller class" (a collection of page
178 handler methods) which represents the root of the application.
179 This may also be an Application instance, or None if using
180 a dispatcher other than the default.
181
182 script_name
183 A string containing the "mount point" of the application.
184 This should start with a slash, and be the path portion of the
185 URL at which to mount the given root. For example, if root.index()
186 will handle requests to "http://www.example.com:8080/dept/app1/",
187 then the script_name argument would be "/dept/app1".
188
189 It MUST NOT end in a slash. If the script_name refers to the
190 root of the URI, it MUST be an empty string (not "/").
191
192 config
193 A file or dict containing application config.
194 """
195 if script_name is None:
196 raise TypeError(
197 "The 'script_name' argument may not be None. Application "
198 "objects may, however, possess a script_name of None (in "
199 "order to inpect the WSGI environ for SCRIPT_NAME upon each "
200 "request). You cannot mount such Applications on this Tree; "
201 "you must pass them to a WSGI server interface directly.")
202
203
204 script_name = script_name.rstrip("/")
205
206 if isinstance(root, Application):
207 app = root
208 if script_name != "" and script_name != app.script_name:
209 raise ValueError(
210 "Cannot specify a different script name and pass an "
211 "Application instance to cherrypy.mount")
212 script_name = app.script_name
213 else:
214 app = Application(root, script_name)
215
216
217 if (script_name == "" and root is not None
218 and not hasattr(root, "favicon_ico")):
219 favicon = os.path.join(os.getcwd(), os.path.dirname(__file__),
220 "favicon.ico")
221 root.favicon_ico = tools.staticfile.handler(favicon)
222
223 if config:
224 app.merge(config)
225
226 self.apps[script_name] = app
227
228 return app
229
230 - def graft(self, wsgi_callable, script_name=""):
235
237 """The script_name of the app at the given path, or None.
238
239 If path is None, cherrypy.request is used.
240 """
241 if path is None:
242 try:
243 request = cherrypy.serving.request
244 path = httputil.urljoin(request.script_name,
245 request.path_info)
246 except AttributeError:
247 return None
248
249 while True:
250 if path in self.apps:
251 return path
252
253 if path == "":
254 return None
255
256
257 path = path[:path.rfind("/")]
258
259 - def __call__(self, environ, start_response):
260
261
262
263 env1x = environ
264 if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
265 env1x = _cpwsgi.downgrade_wsgi_ux_to_1x(environ)
266 path = httputil.urljoin(env1x.get('SCRIPT_NAME', ''),
267 env1x.get('PATH_INFO', ''))
268 sn = self.script_name(path or "/")
269 if sn is None:
270 start_response('404 Not Found', [])
271 return []
272
273 app = self.apps[sn]
274
275
276 environ = environ.copy()
277 if not py3k:
278 if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
279
280 enc = environ[ntou('wsgi.url_encoding')]
281 environ[ntou('SCRIPT_NAME')] = sn.decode(enc)
282 environ[ntou('PATH_INFO')] = path[
283 len(sn.rstrip("/")):].decode(enc)
284 else:
285
286 environ['SCRIPT_NAME'] = sn
287 environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
288 else:
289 if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
290
291 environ['SCRIPT_NAME'] = sn
292 environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
293 else:
294
295 environ['SCRIPT_NAME'] = sn.encode(
296 'utf-8').decode('ISO-8859-1')
297 environ['PATH_INFO'] = path[
298 len(sn.rstrip("/")):].encode('utf-8').decode('ISO-8859-1')
299 return app(environ, start_response)
300