Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/tags/cherrypy-3.0.0/cherrypy/_cpdispatch.py

Revision 1470 (checked in by fumanchu, 3 years ago)

Fix for #614 (VirtualHost? and staticdir tool still don't play well together).

  • Property svn:eol-style set to native
Line 
1 """CherryPy dispatchers."""
2
3 import cherrypy
4
5
6 class PageHandler(object):
7     """Callable which sets response.body."""
8    
9     def __init__(self, callable, *args, **kwargs):
10         self.callable = callable
11         self.args = args
12         self.kwargs = kwargs
13    
14     def __call__(self):
15         return self.callable(*self.args, **self.kwargs)
16
17
18 class LateParamPageHandler(PageHandler):
19     """When passing cherrypy.request.params to the page handler, we do not
20     want to capture that dict too early; we want to give tools like the
21     decoding tool a chance to modify the params dict in-between the lookup
22     of the handler and the actual calling of the handler. This subclass
23     takes that into account, and allows request.params to be 'bound late'
24     (it's more complicated than that, but that's the effect).
25     """
26    
27     def _get_kwargs(self):
28         kwargs = cherrypy.request.params.copy()
29         if self._kwargs:
30             kwargs.update(self._kwargs)
31         return kwargs
32    
33     def _set_kwargs(self, kwargs):
34         self._kwargs = kwargs
35    
36     kwargs = property(_get_kwargs, _set_kwargs,
37                       doc='page handler kwargs (with '
38                       'cherrypy.request.params copied in)')
39
40
41 class Dispatcher(object):
42    
43     def __call__(self, path_info):
44         """Set handler and config for the current request."""
45         request = cherrypy.request
46         func, vpath = self.find_handler(path_info)
47        
48         if func:
49             # Decode any leftover %2F in the virtual_path atoms.
50             vpath = [x.replace("%2F", "/") for x in vpath]
51             request.handler = LateParamPageHandler(func, *vpath)
52         else:
53             request.handler = cherrypy.NotFound()
54    
55     def find_handler(self, path):
56         """Return the appropriate page handler, plus any virtual path.
57         
58         This will return two objects. The first will be a callable,
59         which can be used to generate page output. Any parameters from
60         the query string or request body will be sent to that callable
61         as keyword arguments.
62         
63         The callable is found by traversing the application's tree,
64         starting from cherrypy.request.app.root, and matching path
65         components to successive objects in the tree. For example, the
66         URL "/path/to/handler" might return root.path.to.handler.
67         
68         The second object returned will be a list of names which are
69         'virtual path' components: parts of the URL which are dynamic,
70         and were not used when looking up the handler.
71         These virtual path components are passed to the handler as
72         positional arguments.
73         """
74         request = cherrypy.request
75         app = request.app
76         root = app.root
77        
78         # Get config for the root object/path.
79         curpath = ""
80         nodeconf = {}
81         if hasattr(root, "_cp_config"):
82             nodeconf.update(root._cp_config)
83         if "/" in app.config:
84             nodeconf.update(app.config["/"])
85         object_trail = [['root', root, nodeconf, curpath]]
86        
87         node = root
88         names = [x for x in path.strip('/').split('/') if x] + ['index']
89         for name in names:
90             # map to legal Python identifiers (replace '.' with '_')
91             objname = name.replace('.', '_')
92            
93             nodeconf = {}
94             node = getattr(node, objname, None)
95             if node is not None:
96                 # Get _cp_config attached to this node.
97                 if hasattr(node, "_cp_config"):
98                     nodeconf.update(node._cp_config)
99            
100             # Mix in values from app.config for this path.
101             curpath = "/".join((curpath, name))
102             if curpath in app.config:
103                 nodeconf.update(app.config[curpath])
104            
105             object_trail.append([name, node, nodeconf, curpath])
106        
107         def set_conf():
108             """Collapse all object_trail config into cherrypy.request.config."""
109             base = cherrypy.config.copy()
110             # Note that we merge the config from each node
111             # even if that node was None.
112             for name, obj, conf, curpath in object_trail:
113                 base.update(conf)
114                 if 'tools.staticdir.dir' in conf:
115                     base['tools.staticdir.section'] = curpath
116             return base
117        
118         # Try successive objects (reverse order)
119         num_candidates = len(object_trail) - 1
120         for i in xrange(num_candidates, -1, -1):
121            
122             name, candidate, nodeconf, curpath = object_trail[i]
123             if candidate is None:
124                 continue
125            
126             # Try a "default" method on the current leaf.
127             if hasattr(candidate, "default"):
128                 defhandler = candidate.default
129                 if getattr(defhandler, 'exposed', False):
130                     # Insert any extra _cp_config from the default handler.
131                     conf = getattr(defhandler, "_cp_config", {})
132                     object_trail.insert(i+1, ["default", defhandler, conf, curpath])
133                     request.config = set_conf()
134                     # See http://www.cherrypy.org/ticket/613
135                     request.is_index = path.endswith("/")
136                     return defhandler, names[i:-1]
137            
138             # Uncomment the next line to restrict positional params to "default".
139             # if i < num_candidates - 2: continue
140            
141             # Try the current leaf.
142             if getattr(candidate, 'exposed', False):
143                 request.config = set_conf()
144                 if i == num_candidates:
145                     # We found the extra ".index". Mark request so tools
146                     # can redirect if path_info has no trailing slash.
147                     request.is_index = True
148                 else:
149                     # We're not at an 'index' handler. Mark request so tools
150                     # can redirect if path_info has NO trailing slash.
151                     # Note that this also includes handlers which take
152                     # positional parameters (virtual paths).
153                     request.is_index = False
154                 return candidate, names[i:-1]
155        
156         # We didn't find anything
157         request.config = set_conf()
158         return None, []
159
160
161 class MethodDispatcher(Dispatcher):
162     """Additional dispatch based on cherrypy.request.method.upper().
163     
164     Methods named GET, POST, etc will be called on an exposed class.
165     The method names must be all caps; the appropriate Allow header
166     will be output showing all capitalized method names as allowable
167     HTTP verbs.
168     
169     Note that the containing class must be exposed, not the methods.
170     """
171    
172     def __call__(self, path_info):
173         """Set handler and config for the current request."""
174         request = cherrypy.request
175         resource, vpath = self.find_handler(path_info)
176        
177         if resource:
178             # Set Allow header
179             avail = [m for m in dir(resource) if m.isupper()]
180             if "GET" in avail and "HEAD" not in avail:
181                 avail.append("HEAD")
182             avail.sort()
183             cherrypy.response.headers['Allow'] = ", ".join(avail)
184            
185             # Find the subhandler
186             meth = request.method.upper()
187             func = getattr(resource, meth, None)
188             if func is None and meth == "HEAD":
189                 func = getattr(resource, "GET", None)
190             if func:
191                 # Decode any leftover %2F in the virtual_path atoms.
192                 vpath = [x.replace("%2F", "/") for x in vpath]
193                 request.handler = LateParamPageHandler(func, *vpath)
194             else:
195                 request.handler = cherrypy.HTTPError(405)
196         else:
197             request.handler = cherrypy.NotFound()
198
199
200 class WSGIEnvProxy(object):
201    
202     def __getattr__(self, key):
203         return getattr(cherrypy.request.wsgi_environ, key)
204
205
206 class RoutesDispatcher(object):
207     """A Routes based dispatcher for CherryPy."""
208    
209     def __init__(self, full_result=False):
210         """
211         Routes dispatcher
212
213         Set full_result to True if you wish the controller
214         and the action to be passed on to the page handler
215         parameters. By default they won't be.
216         """
217         import routes
218         self.full_result = full_result
219         self.controllers = {}
220         self.mapper = routes.Mapper()
221         self.mapper.controller_scan = self.controllers.keys
222        
223         # Since Routes' mapper.environ is not threadsafe,
224         # we must use a proxy which does JIT lookup.
225         self.mapper.environ = WSGIEnvProxy()
226    
227     def connect(self, name, route, controller, **kwargs):
228         self.controllers[name] = controller
229         self.mapper.connect(name, route, controller=name, **kwargs)
230    
231     def redirect(self, url):
232         raise cherrypy.HTTPRedirect(url)
233    
234     def __call__(self, path_info):
235         """Set handler and config for the current request."""
236         func = self.find_handler(path_info)
237         if func:
238             cherrypy.request.handler = LateParamPageHandler(func)
239         else:
240             cherrypy.request.handler = cherrypy.NotFound()
241    
242     def find_handler(self, path_info):
243         """Find the right page handler, and set request.config."""
244         import routes
245        
246         request = cherrypy.request
247        
248         config = routes.request_config()
249         config.mapper = self.mapper
250         config.host = request.headers.get('Host', None)
251         config.protocol = request.scheme
252         config.redirect = self.redirect
253        
254         result = self.mapper.match(path_info)
255         config.mapper_dict = result
256         params = {}
257         if result:
258             params = result.copy()
259         if not self.full_result:
260             params.pop('controller', None)
261             params.pop('action', None)
262         request.params.update(params)
263        
264         # Get config for the root object/path.
265         request.config = base = cherrypy.config.copy()
266         curpath = ""
267        
268         def merge(nodeconf):
269             if 'tools.staticdir.dir' in nodeconf:
270                 nodeconf['tools.staticdir.section'] = curpath or "/"
271             base.update(nodeconf)
272        
273         app = request.app
274         root = app.root
275         if hasattr(root, "_cp_config"):
276             merge(root._cp_config)
277         if "/" in app.config:
278             merge(app.config["/"])
279        
280         # Mix in values from app.config.
281         atoms = [x for x in path_info.split("/") if x]
282         if atoms:
283             last = atoms.pop()
284         else:
285             last = None
286         for atom in atoms:
287             curpath = "/".join((curpath, atom))
288             if curpath in app.config:
289                 merge(app.config[curpath])
290        
291         handler = None
292         if result:
293             controller = result.get('controller', None)
294             controller = self.controllers.get(controller)
295             if controller:
296                 # Get config from the controller.
297                 if hasattr(controller, "_cp_config"):
298                     merge(controller._cp_config)
299            
300             action = result.get('action', None)
301             if action is not None:
302                 handler = getattr(controller, action)
303        
304         # Do the last path atom here so it can
305         # override the controller's _cp_config.
306         if last:
307             curpath = "/".join((curpath, last))
308             if curpath in app.config:
309                 merge(app.config[curpath])
310        
311         return handler
312
313
314 def XMLRPCDispatcher(next_dispatcher=Dispatcher()):
315     from cherrypy.lib import xmlrpc
316     def xmlrpc_dispatch(path_info):
317         path_info = xmlrpc.patched_path(path_info)
318         return next_dispatcher(path_info)
319     return xmlrpc_dispatch
320
321
322 def VirtualHost(next_dispatcher=Dispatcher(), use_x_forwarded_host=True, **domains):
323     """Select a different handler based on the Host header.
324     
325     Useful when running multiple sites within one CP server.
326     
327     From http://groups.google.com/group/cherrypy-users/browse_thread/thread/f393540fe278e54d:
328     
329     For various reasons I need several domains to point to different parts of a
330     single website structure as well as to their own "homepage"   EG
331     
332     http://www.mydom1.com  ->  root
333     http://www.mydom2.com  ->  root/mydom2/
334     http://www.mydom3.com  ->  root/mydom3/
335     http://www.mydom4.com  ->  under construction page
336     
337     but also to have  http://www.mydom1.com/mydom2/  etc to be valid pages in
338     their own right.
339     """
340     from cherrypy.lib import http
341     def vhost_dispatch(path_info):
342         header = cherrypy.request.headers.get
343        
344         domain = header('Host', '')
345         if use_x_forwarded_host:
346             domain = header("X-Forwarded-Host", domain)
347        
348         prefix = domains.get(domain, "")
349         if prefix:
350             path_info = http.urljoin(prefix, path_info)
351        
352         result = next_dispatcher(path_info)
353        
354         # Touch up staticdir config. See http://www.cherrypy.org/ticket/614.
355         section = cherrypy.request.config.get('tools.staticdir.section')
356         if section:
357             section = section[len(prefix):]
358             cherrypy.request.config['tools.staticdir.section'] = section
359        
360         return result
361     return vhost_dispatch
362
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets