| 99 | | |
|---|
| 100 | | |
|---|
| 101 | | class PageHandler(object): |
|---|
| 102 | | """Callable which sets response.body.""" |
|---|
| 103 | | |
|---|
| 104 | | def __init__(self, callable, *args, **kwargs): |
|---|
| 105 | | self.callable = callable |
|---|
| 106 | | self.args = args |
|---|
| 107 | | self.kwargs = kwargs |
|---|
| 108 | | |
|---|
| 109 | | def __call__(self): |
|---|
| 110 | | return self.callable(*self.args, **self.kwargs) |
|---|
| 111 | | |
|---|
| 112 | | |
|---|
| 113 | | class LateParamPageHandler(PageHandler): |
|---|
| 114 | | """When passing cherrypy.request.params to the page handler, we do not |
|---|
| 115 | | want to capture that dict too early; we want to give tools like the |
|---|
| 116 | | decoding tool a chance to modify the params dict in-between the lookup |
|---|
| 117 | | of the handler and the actual calling of the handler. This subclass |
|---|
| 118 | | takes that into account, and allows request.params to be 'bound late' |
|---|
| 119 | | (it's more complicated than that, but that's the effect). |
|---|
| 120 | | """ |
|---|
| 121 | | |
|---|
| 122 | | def _get_kwargs(self): |
|---|
| 123 | | kwargs = cherrypy.request.params.copy() |
|---|
| 124 | | if self._kwargs: |
|---|
| 125 | | kwargs.update(self._kwargs) |
|---|
| 126 | | return kwargs |
|---|
| 127 | | |
|---|
| 128 | | def _set_kwargs(self, kwargs): |
|---|
| 129 | | self._kwargs = kwargs |
|---|
| 130 | | |
|---|
| 131 | | kwargs = property(_get_kwargs, _set_kwargs, |
|---|
| 132 | | doc='page handler kwargs (with ' |
|---|
| 133 | | 'cherrypy.request.params copied in)') |
|---|
| 134 | | |
|---|
| 135 | | |
|---|
| 136 | | class Dispatcher(object): |
|---|
| 137 | | |
|---|
| 138 | | def __call__(self, path_info): |
|---|
| 139 | | """Set handler and config for the current request.""" |
|---|
| 140 | | request = cherrypy.request |
|---|
| 141 | | func, vpath = self.find_handler(path_info) |
|---|
| 142 | | |
|---|
| 143 | | if func: |
|---|
| 144 | | # Decode any leftover %2F in the virtual_path atoms. |
|---|
| 145 | | vpath = [x.replace("%2F", "/") for x in vpath] |
|---|
| 146 | | request.handler = LateParamPageHandler(func, *vpath) |
|---|
| 147 | | else: |
|---|
| 148 | | request.handler = cherrypy.NotFound() |
|---|
| 149 | | |
|---|
| 150 | | def find_handler(self, path): |
|---|
| 151 | | """Return the appropriate page handler, plus any virtual path. |
|---|
| 152 | | |
|---|
| 153 | | This will return two objects. The first will be a callable, |
|---|
| 154 | | which can be used to generate page output. Any parameters from |
|---|
| 155 | | the query string or request body will be sent to that callable |
|---|
| 156 | | as keyword arguments. |
|---|
| 157 | | |
|---|
| 158 | | The callable is found by traversing the application's tree, |
|---|
| 159 | | starting from cherrypy.request.app.root, and matching path |
|---|
| 160 | | components to successive objects in the tree. For example, the |
|---|
| 161 | | URL "/path/to/handler" might return root.path.to.handler. |
|---|
| 162 | | |
|---|
| 163 | | The second object returned will be a list of names which are |
|---|
| 164 | | 'virtual path' components: parts of the URL which are dynamic, |
|---|
| 165 | | and were not used when looking up the handler. |
|---|
| 166 | | These virtual path components are passed to the handler as |
|---|
| 167 | | positional arguments. |
|---|
| 168 | | """ |
|---|
| 169 | | request = cherrypy.request |
|---|
| 170 | | app = request.app |
|---|
| 171 | | root = app.root |
|---|
| 172 | | |
|---|
| 173 | | # Get config for the root object/path. |
|---|
| 174 | | curpath = "" |
|---|
| 175 | | nodeconf = {} |
|---|
| 176 | | if hasattr(root, "_cp_config"): |
|---|
| 177 | | nodeconf.update(root._cp_config) |
|---|
| 178 | | if "/" in app.config: |
|---|
| 179 | | nodeconf.update(app.config["/"]) |
|---|
| 180 | | object_trail = [['root', root, nodeconf, curpath]] |
|---|
| 181 | | |
|---|
| 182 | | node = root |
|---|
| 183 | | names = [x for x in path.strip('/').split('/') if x] + ['index'] |
|---|
| 184 | | for name in names: |
|---|
| 185 | | # map to legal Python identifiers (replace '.' with '_') |
|---|
| 186 | | objname = name.replace('.', '_') |
|---|
| 187 | | |
|---|
| 188 | | nodeconf = {} |
|---|
| 189 | | node = getattr(node, objname, None) |
|---|
| 190 | | if node is not None: |
|---|
| 191 | | # Get _cp_config attached to this node. |
|---|
| 192 | | if hasattr(node, "_cp_config"): |
|---|
| 193 | | nodeconf.update(node._cp_config) |
|---|
| 194 | | |
|---|
| 195 | | # Mix in values from app.config for this path. |
|---|
| 196 | | curpath = "/".join((curpath, name)) |
|---|
| 197 | | if curpath in app.config: |
|---|
| 198 | | nodeconf.update(app.config[curpath]) |
|---|
| 199 | | |
|---|
| 200 | | object_trail.append([name, node, nodeconf, curpath]) |
|---|
| 201 | | |
|---|
| 202 | | def set_conf(): |
|---|
| 203 | | """Set cherrypy.request.config.""" |
|---|
| 204 | | base = cherrypy.config.copy() |
|---|
| 205 | | # Note that we merge the config from each node |
|---|
| 206 | | # even if that node was None. |
|---|
| 207 | | for name, obj, conf, curpath in object_trail: |
|---|
| 208 | | base.update(conf) |
|---|
| 209 | | if 'tools.staticdir.dir' in conf: |
|---|
| 210 | | base['tools.staticdir.section'] = curpath |
|---|
| 211 | | return base |
|---|
| 212 | | |
|---|
| 213 | | # Try successive objects (reverse order) |
|---|
| 214 | | num_candidates = len(object_trail) - 1 |
|---|
| 215 | | for i in xrange(num_candidates, -1, -1): |
|---|
| 216 | | |
|---|
| 217 | | name, candidate, nodeconf, curpath = object_trail[i] |
|---|
| 218 | | if candidate is None: |
|---|
| 219 | | continue |
|---|
| 220 | | |
|---|
| 221 | | # Try a "default" method on the current leaf. |
|---|
| 222 | | if hasattr(candidate, "default"): |
|---|
| 223 | | defhandler = candidate.default |
|---|
| 224 | | if getattr(defhandler, 'exposed', False): |
|---|
| 225 | | # Insert any extra _cp_config from the default handler. |
|---|
| 226 | | conf = getattr(defhandler, "_cp_config", {}) |
|---|
| 227 | | object_trail.insert(i+1, ["default", defhandler, conf, curpath]) |
|---|
| 228 | | request.config = set_conf() |
|---|
| 229 | | request.is_index = False |
|---|
| 230 | | return defhandler, names[i:-1] |
|---|
| 231 | | |
|---|
| 232 | | # Uncomment the next line to restrict positional params to "default". |
|---|
| 233 | | # if i < num_candidates - 2: continue |
|---|
| 234 | | |
|---|
| 235 | | # Try the current leaf. |
|---|
| 236 | | if getattr(candidate, 'exposed', False): |
|---|
| 237 | | request.config = set_conf() |
|---|
| 238 | | if i == num_candidates: |
|---|
| 239 | | # We found the extra ".index". Mark request so tools |
|---|
| 240 | | # can redirect if path_info has no trailing slash. |
|---|
| 241 | | request.is_index = True |
|---|
| 242 | | else: |
|---|
| 243 | | # We're not at an 'index' handler. Mark request so tools |
|---|
| 244 | | # can redirect if path_info has NO trailing slash. |
|---|
| 245 | | # Note that this also includes handlers which take |
|---|
| 246 | | # positional parameters (virtual paths). |
|---|
| 247 | | request.is_index = False |
|---|
| 248 | | return candidate, names[i:-1] |
|---|
| 249 | | |
|---|
| 250 | | # We didn't find anything |
|---|
| 251 | | request.config = set_conf() |
|---|
| 252 | | return None, [] |
|---|
| 253 | | |
|---|
| 254 | | |
|---|
| 255 | | class MethodDispatcher(Dispatcher): |
|---|
| 256 | | """Additional dispatch based on cherrypy.request.method.upper(). |
|---|
| 257 | | |
|---|
| 258 | | Methods named GET, POST, etc will be called on an exposed class. |
|---|
| 259 | | The method names must be all caps; the appropriate Allow header |
|---|
| 260 | | will be output showing all capitalized method names as allowable |
|---|
| 261 | | HTTP verbs. |
|---|
| 262 | | |
|---|
| 263 | | Note that the containing class must be exposed, not the methods. |
|---|
| 264 | | """ |
|---|
| 265 | | |
|---|
| 266 | | def __call__(self, path_info): |
|---|
| 267 | | """Set handler and config for the current request.""" |
|---|
| 268 | | request = cherrypy.request |
|---|
| 269 | | resource, vpath = self.find_handler(path_info) |
|---|
| 270 | | |
|---|
| 271 | | if resource: |
|---|
| 272 | | # Set Allow header |
|---|
| 273 | | avail = [m for m in dir(resource) if m.isupper()] |
|---|
| 274 | | if "GET" in avail and "HEAD" not in avail: |
|---|
| 275 | | avail.append("HEAD") |
|---|
| 276 | | avail.sort() |
|---|
| 277 | | cherrypy.response.headers['Allow'] = ", ".join(avail) |
|---|
| 278 | | |
|---|
| 279 | | # Find the subhandler |
|---|
| 280 | | meth = request.method.upper() |
|---|
| 281 | | func = getattr(resource, meth, None) |
|---|
| 282 | | if func is None and meth == "HEAD": |
|---|
| 283 | | func = getattr(resource, "GET", None) |
|---|
| 284 | | if func: |
|---|
| 285 | | # Decode any leftover %2F in the virtual_path atoms. |
|---|
| 286 | | vpath = [x.replace("%2F", "/") for x in vpath] |
|---|
| 287 | | request.handler = LateParamPageHandler(func, *vpath) |
|---|
| 288 | | else: |
|---|
| 289 | | request.handler = cherrypy.HTTPError(405) |
|---|
| 290 | | else: |
|---|
| 291 | | request.handler = cherrypy.NotFound() |
|---|