1 """CherryPy dispatchers.
2
3 A 'dispatcher' is the object which looks up the 'page handler' callable
4 and collects config for the current request based on the path_info, other
5 request attributes, and the application architecture. The core calls the
6 dispatcher as early as possible, passing it a 'path_info' argument.
7
8 The default dispatcher discovers the page handler by matching path_info
9 to a hierarchical arrangement of objects, starting at request.app.root.
10 """
11
12 import cherrypy
13
14
15 -class PageHandler(object):
16 """Callable which sets response.body."""
17
18 - def __init__(self, callable, *args, **kwargs):
19 self.callable = callable
20 self.args = args
21 self.kwargs = kwargs
22
24 return self.callable(*self.args, **self.kwargs)
25
26
27 -class LateParamPageHandler(PageHandler):
28 """When passing cherrypy.request.params to the page handler, we do not
29 want to capture that dict too early; we want to give tools like the
30 decoding tool a chance to modify the params dict in-between the lookup
31 of the handler and the actual calling of the handler. This subclass
32 takes that into account, and allows request.params to be 'bound late'
33 (it's more complicated than that, but that's the effect).
34 """
35
36 - def _get_kwargs(self):
37 kwargs = cherrypy.request.params.copy()
38 if self._kwargs:
39 kwargs.update(self._kwargs)
40 return kwargs
41
42 - def _set_kwargs(self, kwargs):
44
45 kwargs = property(_get_kwargs, _set_kwargs,
46 doc='page handler kwargs (with '
47 'cherrypy.request.params copied in)')
48
49
51 """CherryPy Dispatcher which walks a tree of objects to find a handler.
52
53 The tree is rooted at cherrypy.request.app.root, and each hierarchical
54 component in the path_info argument is matched to a corresponding nested
55 attribute of the root object. Matching handlers must have an 'exposed'
56 attribute which evaluates to True. The special method name "index"
57 matches a URI which ends in a slash ("/"). The special method name
58 "default" may match a portion of the path_info (but only when no longer
59 substring of the path_info matches some other object).
60
61 This is the default, built-in dispatcher for CherryPy.
62 """
63
75
77 """Return the appropriate page handler, plus any virtual path.
78
79 This will return two objects. The first will be a callable,
80 which can be used to generate page output. Any parameters from
81 the query string or request body will be sent to that callable
82 as keyword arguments.
83
84 The callable is found by traversing the application's tree,
85 starting from cherrypy.request.app.root, and matching path
86 components to successive objects in the tree. For example, the
87 URL "/path/to/handler" might return root.path.to.handler.
88
89 The second object returned will be a list of names which are
90 'virtual path' components: parts of the URL which are dynamic,
91 and were not used when looking up the handler.
92 These virtual path components are passed to the handler as
93 positional arguments.
94 """
95 request = cherrypy.request
96 app = request.app
97 root = app.root
98
99
100 curpath = ""
101 nodeconf = {}
102 if hasattr(root, "_cp_config"):
103 nodeconf.update(root._cp_config)
104 if "/" in app.config:
105 nodeconf.update(app.config["/"])
106 object_trail = [['root', root, nodeconf, curpath]]
107
108 node = root
109 names = [x for x in path.strip('/').split('/') if x] + ['index']
110 for name in names:
111
112 objname = name.replace('.', '_')
113
114 nodeconf = {}
115 node = getattr(node, objname, None)
116 if node is not None:
117
118 if hasattr(node, "_cp_config"):
119 nodeconf.update(node._cp_config)
120
121
122 curpath = "/".join((curpath, name))
123 if curpath in app.config:
124 nodeconf.update(app.config[curpath])
125
126 object_trail.append([name, node, nodeconf, curpath])
127
128 def set_conf():
129 """Collapse all object_trail config into cherrypy.request.config."""
130 base = cherrypy.config.copy()
131
132
133 for name, obj, conf, curpath in object_trail:
134 base.update(conf)
135 if 'tools.staticdir.dir' in conf:
136 base['tools.staticdir.section'] = curpath
137 return base
138
139
140 num_candidates = len(object_trail) - 1
141 for i in xrange(num_candidates, -1, -1):
142
143 name, candidate, nodeconf, curpath = object_trail[i]
144 if candidate is None:
145 continue
146
147
148 if hasattr(candidate, "default"):
149 defhandler = candidate.default
150 if getattr(defhandler, 'exposed', False):
151
152 conf = getattr(defhandler, "_cp_config", {})
153 object_trail.insert(i+1, ["default", defhandler, conf, curpath])
154 request.config = set_conf()
155
156 request.is_index = path.endswith("/")
157 return defhandler, names[i:-1]
158
159
160
161
162
163 if getattr(candidate, 'exposed', False):
164 request.config = set_conf()
165 if i == num_candidates:
166
167
168 request.is_index = True
169 else:
170
171
172
173
174 request.is_index = False
175 return candidate, names[i:-1]
176
177
178 request.config = set_conf()
179 return None, []
180
181
183 """Additional dispatch based on cherrypy.request.method.upper().
184
185 Methods named GET, POST, etc will be called on an exposed class.
186 The method names must be all caps; the appropriate Allow header
187 will be output showing all capitalized method names as allowable
188 HTTP verbs.
189
190 Note that the containing class must be exposed, not the methods.
191 """
192
219
220
225
226
228 """A Routes based dispatcher for CherryPy."""
229
231 """
232 Routes dispatcher
233
234 Set full_result to True if you wish the controller
235 and the action to be passed on to the page handler
236 parameters. By default they won't be.
237 """
238 import routes
239 self.full_result = full_result
240 self.controllers = {}
241 self.mapper = routes.Mapper()
242 self.mapper.controller_scan = self.controllers.keys
243
244 - def connect(self, name, route, controller, **kwargs):
247
250
258
293
294 app = request.app
295 root = app.root
296 if hasattr(root, "_cp_config"):
297 merge(root._cp_config)
298 if "/" in app.config:
299 merge(app.config["/"])
300
301
302 atoms = [x for x in path_info.split("/") if x]
303 if atoms:
304 last = atoms.pop()
305 else:
306 last = None
307 for atom in atoms:
308 curpath = "/".join((curpath, atom))
309 if curpath in app.config:
310 merge(app.config[curpath])
311
312 handler = None
313 if result:
314 controller = result.get('controller', None)
315 controller = self.controllers.get(controller)
316 if controller:
317
318 if hasattr(controller, "_cp_config"):
319 merge(controller._cp_config)
320
321 action = result.get('action', None)
322 if action is not None:
323 handler = getattr(controller, action)
324
325 if hasattr(handler, "_cp_config"):
326 merge(handler._cp_config)
327
328
329
330 if last:
331 curpath = "/".join((curpath, last))
332 if curpath in app.config:
333 merge(app.config[curpath])
334
335 return handler
336
337
343 return xmlrpc_dispatch
344
345
347 """Select a different handler based on the Host header.
348
349 Useful when running multiple sites within one CP server.
350
351 From http://groups.google.com/group/cherrypy-users/browse_thread/thread/f393540fe278e54d:
352
353 For various reasons I need several domains to point to different parts of a
354 single website structure as well as to their own "homepage" EG
355
356 http://www.mydom1.com -> root
357 http://www.mydom2.com -> root/mydom2/
358 http://www.mydom3.com -> root/mydom3/
359 http://www.mydom4.com -> under construction page
360
361 but also to have http://www.mydom1.com/mydom2/ etc to be valid pages in
362 their own right.
363 """
364 from cherrypy.lib import http
365 def vhost_dispatch(path_info):
366 header = cherrypy.request.headers.get
367
368 domain = header('Host', '')
369 if use_x_forwarded_host:
370 domain = header("X-Forwarded-Host", domain)
371
372 prefix = domains.get(domain, "")
373 if prefix:
374 path_info = http.urljoin(prefix, path_info)
375
376 result = next_dispatcher(path_info)
377
378
379 section = cherrypy.request.config.get('tools.staticdir.section')
380 if section:
381 section = section[len(prefix):]
382 cherrypy.request.config['tools.staticdir.section'] = section
383
384 return result
385 return vhost_dispatch
386