OLD | NEW |
(Empty) | |
| 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 string |
| 13 import sys |
| 14 import types |
| 15 try: |
| 16 classtype = (type, types.ClassType) |
| 17 except AttributeError: |
| 18 classtype = type |
| 19 |
| 20 import cherrypy |
| 21 from cherrypy._cpcompat import set |
| 22 |
| 23 |
| 24 class PageHandler(object): |
| 25 """Callable which sets response.body.""" |
| 26 |
| 27 def __init__(self, callable, *args, **kwargs): |
| 28 self.callable = callable |
| 29 self.args = args |
| 30 self.kwargs = kwargs |
| 31 |
| 32 def __call__(self): |
| 33 try: |
| 34 return self.callable(*self.args, **self.kwargs) |
| 35 except TypeError: |
| 36 x = sys.exc_info()[1] |
| 37 try: |
| 38 test_callable_spec(self.callable, self.args, self.kwargs) |
| 39 except cherrypy.HTTPError: |
| 40 raise sys.exc_info()[1] |
| 41 except: |
| 42 raise x |
| 43 raise |
| 44 |
| 45 |
| 46 def test_callable_spec(callable, callable_args, callable_kwargs): |
| 47 """ |
| 48 Inspect callable and test to see if the given args are suitable for it. |
| 49 |
| 50 When an error occurs during the handler's invoking stage there are 2 |
| 51 erroneous cases: |
| 52 1. Too many parameters passed to a function which doesn't define |
| 53 one of *args or **kwargs. |
| 54 2. Too little parameters are passed to the function. |
| 55 |
| 56 There are 3 sources of parameters to a cherrypy handler. |
| 57 1. query string parameters are passed as keyword parameters to the handler. |
| 58 2. body parameters are also passed as keyword parameters. |
| 59 3. when partial matching occurs, the final path atoms are passed as |
| 60 positional args. |
| 61 Both the query string and path atoms are part of the URI. If they are |
| 62 incorrect, then a 404 Not Found should be raised. Conversely the body |
| 63 parameters are part of the request; if they are invalid a 400 Bad Request. |
| 64 """ |
| 65 show_mismatched_params = getattr( |
| 66 cherrypy.serving.request, 'show_mismatched_params', False) |
| 67 try: |
| 68 (args, varargs, varkw, defaults) = inspect.getargspec(callable) |
| 69 except TypeError: |
| 70 if isinstance(callable, object) and hasattr(callable, '__call__'): |
| 71 (args, varargs, varkw, defaults) = inspect.getargspec(callable.__cal
l__) |
| 72 else: |
| 73 # If it wasn't one of our own types, re-raise |
| 74 # the original error |
| 75 raise |
| 76 |
| 77 if args and args[0] == 'self': |
| 78 args = args[1:] |
| 79 |
| 80 arg_usage = dict([(arg, 0,) for arg in args]) |
| 81 vararg_usage = 0 |
| 82 varkw_usage = 0 |
| 83 extra_kwargs = set() |
| 84 |
| 85 for i, value in enumerate(callable_args): |
| 86 try: |
| 87 arg_usage[args[i]] += 1 |
| 88 except IndexError: |
| 89 vararg_usage += 1 |
| 90 |
| 91 for key in callable_kwargs.keys(): |
| 92 try: |
| 93 arg_usage[key] += 1 |
| 94 except KeyError: |
| 95 varkw_usage += 1 |
| 96 extra_kwargs.add(key) |
| 97 |
| 98 # figure out which args have defaults. |
| 99 args_with_defaults = args[-len(defaults or []):] |
| 100 for i, val in enumerate(defaults or []): |
| 101 # Defaults take effect only when the arg hasn't been used yet. |
| 102 if arg_usage[args_with_defaults[i]] == 0: |
| 103 arg_usage[args_with_defaults[i]] += 1 |
| 104 |
| 105 missing_args = [] |
| 106 multiple_args = [] |
| 107 for key, usage in arg_usage.items(): |
| 108 if usage == 0: |
| 109 missing_args.append(key) |
| 110 elif usage > 1: |
| 111 multiple_args.append(key) |
| 112 |
| 113 if missing_args: |
| 114 # In the case where the method allows body arguments |
| 115 # there are 3 potential errors: |
| 116 # 1. not enough query string parameters -> 404 |
| 117 # 2. not enough body parameters -> 400 |
| 118 # 3. not enough path parts (partial matches) -> 404 |
| 119 # |
| 120 # We can't actually tell which case it is, |
| 121 # so I'm raising a 404 because that covers 2/3 of the |
| 122 # possibilities |
| 123 # |
| 124 # In the case where the method does not allow body |
| 125 # arguments it's definitely a 404. |
| 126 message = None |
| 127 if show_mismatched_params: |
| 128 message="Missing parameters: %s" % ",".join(missing_args) |
| 129 raise cherrypy.HTTPError(404, message=message) |
| 130 |
| 131 # the extra positional arguments come from the path - 404 Not Found |
| 132 if not varargs and vararg_usage > 0: |
| 133 raise cherrypy.HTTPError(404) |
| 134 |
| 135 body_params = cherrypy.serving.request.body.params or {} |
| 136 body_params = set(body_params.keys()) |
| 137 qs_params = set(callable_kwargs.keys()) - body_params |
| 138 |
| 139 if multiple_args: |
| 140 if qs_params.intersection(set(multiple_args)): |
| 141 # If any of the multiple parameters came from the query string then |
| 142 # it's a 404 Not Found |
| 143 error = 404 |
| 144 else: |
| 145 # Otherwise it's a 400 Bad Request |
| 146 error = 400 |
| 147 |
| 148 message = None |
| 149 if show_mismatched_params: |
| 150 message="Multiple values for parameters: "\ |
| 151 "%s" % ",".join(multiple_args) |
| 152 raise cherrypy.HTTPError(error, message=message) |
| 153 |
| 154 if not varkw and varkw_usage > 0: |
| 155 |
| 156 # If there were extra query string parameters, it's a 404 Not Found |
| 157 extra_qs_params = set(qs_params).intersection(extra_kwargs) |
| 158 if extra_qs_params: |
| 159 message = None |
| 160 if show_mismatched_params: |
| 161 message="Unexpected query string "\ |
| 162 "parameters: %s" % ", ".join(extra_qs_params) |
| 163 raise cherrypy.HTTPError(404, message=message) |
| 164 |
| 165 # If there were any extra body parameters, it's a 400 Not Found |
| 166 extra_body_params = set(body_params).intersection(extra_kwargs) |
| 167 if extra_body_params: |
| 168 message = None |
| 169 if show_mismatched_params: |
| 170 message="Unexpected body parameters: "\ |
| 171 "%s" % ", ".join(extra_body_params) |
| 172 raise cherrypy.HTTPError(400, message=message) |
| 173 |
| 174 |
| 175 try: |
| 176 import inspect |
| 177 except ImportError: |
| 178 test_callable_spec = lambda callable, args, kwargs: None |
| 179 |
| 180 |
| 181 |
| 182 class LateParamPageHandler(PageHandler): |
| 183 """When passing cherrypy.request.params to the page handler, we do not |
| 184 want to capture that dict too early; we want to give tools like the |
| 185 decoding tool a chance to modify the params dict in-between the lookup |
| 186 of the handler and the actual calling of the handler. This subclass |
| 187 takes that into account, and allows request.params to be 'bound late' |
| 188 (it's more complicated than that, but that's the effect). |
| 189 """ |
| 190 |
| 191 def _get_kwargs(self): |
| 192 kwargs = cherrypy.serving.request.params.copy() |
| 193 if self._kwargs: |
| 194 kwargs.update(self._kwargs) |
| 195 return kwargs |
| 196 |
| 197 def _set_kwargs(self, kwargs): |
| 198 self._kwargs = kwargs |
| 199 |
| 200 kwargs = property(_get_kwargs, _set_kwargs, |
| 201 doc='page handler kwargs (with ' |
| 202 'cherrypy.request.params copied in)') |
| 203 |
| 204 |
| 205 if sys.version_info < (3, 0): |
| 206 punctuation_to_underscores = string.maketrans( |
| 207 string.punctuation, '_' * len(string.punctuation)) |
| 208 def validate_translator(t): |
| 209 if not isinstance(t, str) or len(t) != 256: |
| 210 raise ValueError("The translate argument must be a str of len 256.") |
| 211 else: |
| 212 punctuation_to_underscores = str.maketrans( |
| 213 string.punctuation, '_' * len(string.punctuation)) |
| 214 def validate_translator(t): |
| 215 if not isinstance(t, dict): |
| 216 raise ValueError("The translate argument must be a dict.") |
| 217 |
| 218 class Dispatcher(object): |
| 219 """CherryPy Dispatcher which walks a tree of objects to find a handler. |
| 220 |
| 221 The tree is rooted at cherrypy.request.app.root, and each hierarchical |
| 222 component in the path_info argument is matched to a corresponding nested |
| 223 attribute of the root object. Matching handlers must have an 'exposed' |
| 224 attribute which evaluates to True. The special method name "index" |
| 225 matches a URI which ends in a slash ("/"). The special method name |
| 226 "default" may match a portion of the path_info (but only when no longer |
| 227 substring of the path_info matches some other object). |
| 228 |
| 229 This is the default, built-in dispatcher for CherryPy. |
| 230 """ |
| 231 |
| 232 dispatch_method_name = '_cp_dispatch' |
| 233 """ |
| 234 The name of the dispatch method that nodes may optionally implement |
| 235 to provide their own dynamic dispatch algorithm. |
| 236 """ |
| 237 |
| 238 def __init__(self, dispatch_method_name=None, |
| 239 translate=punctuation_to_underscores): |
| 240 validate_translator(translate) |
| 241 self.translate = translate |
| 242 if dispatch_method_name: |
| 243 self.dispatch_method_name = dispatch_method_name |
| 244 |
| 245 def __call__(self, path_info): |
| 246 """Set handler and config for the current request.""" |
| 247 request = cherrypy.serving.request |
| 248 func, vpath = self.find_handler(path_info) |
| 249 |
| 250 if func: |
| 251 # Decode any leftover %2F in the virtual_path atoms. |
| 252 vpath = [x.replace("%2F", "/") for x in vpath] |
| 253 request.handler = LateParamPageHandler(func, *vpath) |
| 254 else: |
| 255 request.handler = cherrypy.NotFound() |
| 256 |
| 257 def find_handler(self, path): |
| 258 """Return the appropriate page handler, plus any virtual path. |
| 259 |
| 260 This will return two objects. The first will be a callable, |
| 261 which can be used to generate page output. Any parameters from |
| 262 the query string or request body will be sent to that callable |
| 263 as keyword arguments. |
| 264 |
| 265 The callable is found by traversing the application's tree, |
| 266 starting from cherrypy.request.app.root, and matching path |
| 267 components to successive objects in the tree. For example, the |
| 268 URL "/path/to/handler" might return root.path.to.handler. |
| 269 |
| 270 The second object returned will be a list of names which are |
| 271 'virtual path' components: parts of the URL which are dynamic, |
| 272 and were not used when looking up the handler. |
| 273 These virtual path components are passed to the handler as |
| 274 positional arguments. |
| 275 """ |
| 276 request = cherrypy.serving.request |
| 277 app = request.app |
| 278 root = app.root |
| 279 dispatch_name = self.dispatch_method_name |
| 280 |
| 281 # Get config for the root object/path. |
| 282 fullpath = [x for x in path.strip('/').split('/') if x] + ['index'] |
| 283 fullpath_len = len(fullpath) |
| 284 segleft = fullpath_len |
| 285 nodeconf = {} |
| 286 if hasattr(root, "_cp_config"): |
| 287 nodeconf.update(root._cp_config) |
| 288 if "/" in app.config: |
| 289 nodeconf.update(app.config["/"]) |
| 290 object_trail = [['root', root, nodeconf, segleft]] |
| 291 |
| 292 node = root |
| 293 iternames = fullpath[:] |
| 294 while iternames: |
| 295 name = iternames[0] |
| 296 # map to legal Python identifiers (e.g. replace '.' with '_') |
| 297 objname = name.translate(self.translate) |
| 298 |
| 299 nodeconf = {} |
| 300 subnode = getattr(node, objname, None) |
| 301 pre_len = len(iternames) |
| 302 if subnode is None: |
| 303 dispatch = getattr(node, dispatch_name, None) |
| 304 if dispatch and hasattr(dispatch, '__call__') and not \ |
| 305 getattr(dispatch, 'exposed', False) and \ |
| 306 pre_len > 1: |
| 307 #Don't expose the hidden 'index' token to _cp_dispatch |
| 308 #We skip this if pre_len == 1 since it makes no sense |
| 309 #to call a dispatcher when we have no tokens left. |
| 310 index_name = iternames.pop() |
| 311 subnode = dispatch(vpath=iternames) |
| 312 iternames.append(index_name) |
| 313 else: |
| 314 #We didn't find a path, but keep processing in case there |
| 315 #is a default() handler. |
| 316 iternames.pop(0) |
| 317 else: |
| 318 #We found the path, remove the vpath entry |
| 319 iternames.pop(0) |
| 320 segleft = len(iternames) |
| 321 if segleft > pre_len: |
| 322 #No path segment was removed. Raise an error. |
| 323 raise cherrypy.CherryPyException( |
| 324 "A vpath segment was added. Custom dispatchers may only " |
| 325 + "remove elements. While trying to process " |
| 326 + "{0} in {1}".format(name, fullpath) |
| 327 ) |
| 328 elif segleft == pre_len: |
| 329 #Assume that the handler used the current path segment, but |
| 330 #did not pop it. This allows things like |
| 331 #return getattr(self, vpath[0], None) |
| 332 iternames.pop(0) |
| 333 segleft -= 1 |
| 334 node = subnode |
| 335 |
| 336 if node is not None: |
| 337 # Get _cp_config attached to this node. |
| 338 if hasattr(node, "_cp_config"): |
| 339 nodeconf.update(node._cp_config) |
| 340 |
| 341 # Mix in values from app.config for this path. |
| 342 existing_len = fullpath_len - pre_len |
| 343 if existing_len != 0: |
| 344 curpath = '/' + '/'.join(fullpath[0:existing_len]) |
| 345 else: |
| 346 curpath = '' |
| 347 new_segs = fullpath[fullpath_len - pre_len:fullpath_len - segleft] |
| 348 for seg in new_segs: |
| 349 curpath += '/' + seg |
| 350 if curpath in app.config: |
| 351 nodeconf.update(app.config[curpath]) |
| 352 |
| 353 object_trail.append([name, node, nodeconf, segleft]) |
| 354 |
| 355 def set_conf(): |
| 356 """Collapse all object_trail config into cherrypy.request.config.""" |
| 357 base = cherrypy.config.copy() |
| 358 # Note that we merge the config from each node |
| 359 # even if that node was None. |
| 360 for name, obj, conf, segleft in object_trail: |
| 361 base.update(conf) |
| 362 if 'tools.staticdir.dir' in conf: |
| 363 base['tools.staticdir.section'] = '/' + '/'.join(fullpath[0:
fullpath_len - segleft]) |
| 364 return base |
| 365 |
| 366 # Try successive objects (reverse order) |
| 367 num_candidates = len(object_trail) - 1 |
| 368 for i in range(num_candidates, -1, -1): |
| 369 |
| 370 name, candidate, nodeconf, segleft = object_trail[i] |
| 371 if candidate is None: |
| 372 continue |
| 373 |
| 374 # Try a "default" method on the current leaf. |
| 375 if hasattr(candidate, "default"): |
| 376 defhandler = candidate.default |
| 377 if getattr(defhandler, 'exposed', False): |
| 378 # Insert any extra _cp_config from the default handler. |
| 379 conf = getattr(defhandler, "_cp_config", {}) |
| 380 object_trail.insert(i+1, ["default", defhandler, conf, segle
ft]) |
| 381 request.config = set_conf() |
| 382 # See http://www.cherrypy.org/ticket/613 |
| 383 request.is_index = path.endswith("/") |
| 384 return defhandler, fullpath[fullpath_len - segleft:-1] |
| 385 |
| 386 # Uncomment the next line to restrict positional params to "default"
. |
| 387 # if i < num_candidates - 2: continue |
| 388 |
| 389 # Try the current leaf. |
| 390 if getattr(candidate, 'exposed', False): |
| 391 request.config = set_conf() |
| 392 if i == num_candidates: |
| 393 # We found the extra ".index". Mark request so tools |
| 394 # can redirect if path_info has no trailing slash. |
| 395 request.is_index = True |
| 396 else: |
| 397 # We're not at an 'index' handler. Mark request so tools |
| 398 # can redirect if path_info has NO trailing slash. |
| 399 # Note that this also includes handlers which take |
| 400 # positional parameters (virtual paths). |
| 401 request.is_index = False |
| 402 return candidate, fullpath[fullpath_len - segleft:-1] |
| 403 |
| 404 # We didn't find anything |
| 405 request.config = set_conf() |
| 406 return None, [] |
| 407 |
| 408 |
| 409 class MethodDispatcher(Dispatcher): |
| 410 """Additional dispatch based on cherrypy.request.method.upper(). |
| 411 |
| 412 Methods named GET, POST, etc will be called on an exposed class. |
| 413 The method names must be all caps; the appropriate Allow header |
| 414 will be output showing all capitalized method names as allowable |
| 415 HTTP verbs. |
| 416 |
| 417 Note that the containing class must be exposed, not the methods. |
| 418 """ |
| 419 |
| 420 def __call__(self, path_info): |
| 421 """Set handler and config for the current request.""" |
| 422 request = cherrypy.serving.request |
| 423 resource, vpath = self.find_handler(path_info) |
| 424 |
| 425 if resource: |
| 426 # Set Allow header |
| 427 avail = [m for m in dir(resource) if m.isupper()] |
| 428 if "GET" in avail and "HEAD" not in avail: |
| 429 avail.append("HEAD") |
| 430 avail.sort() |
| 431 cherrypy.serving.response.headers['Allow'] = ", ".join(avail) |
| 432 |
| 433 # Find the subhandler |
| 434 meth = request.method.upper() |
| 435 func = getattr(resource, meth, None) |
| 436 if func is None and meth == "HEAD": |
| 437 func = getattr(resource, "GET", None) |
| 438 if func: |
| 439 # Grab any _cp_config on the subhandler. |
| 440 if hasattr(func, "_cp_config"): |
| 441 request.config.update(func._cp_config) |
| 442 |
| 443 # Decode any leftover %2F in the virtual_path atoms. |
| 444 vpath = [x.replace("%2F", "/") for x in vpath] |
| 445 request.handler = LateParamPageHandler(func, *vpath) |
| 446 else: |
| 447 request.handler = cherrypy.HTTPError(405) |
| 448 else: |
| 449 request.handler = cherrypy.NotFound() |
| 450 |
| 451 |
| 452 class RoutesDispatcher(object): |
| 453 """A Routes based dispatcher for CherryPy.""" |
| 454 |
| 455 def __init__(self, full_result=False): |
| 456 """ |
| 457 Routes dispatcher |
| 458 |
| 459 Set full_result to True if you wish the controller |
| 460 and the action to be passed on to the page handler |
| 461 parameters. By default they won't be. |
| 462 """ |
| 463 import routes |
| 464 self.full_result = full_result |
| 465 self.controllers = {} |
| 466 self.mapper = routes.Mapper() |
| 467 self.mapper.controller_scan = self.controllers.keys |
| 468 |
| 469 def connect(self, name, route, controller, **kwargs): |
| 470 self.controllers[name] = controller |
| 471 self.mapper.connect(name, route, controller=name, **kwargs) |
| 472 |
| 473 def redirect(self, url): |
| 474 raise cherrypy.HTTPRedirect(url) |
| 475 |
| 476 def __call__(self, path_info): |
| 477 """Set handler and config for the current request.""" |
| 478 func = self.find_handler(path_info) |
| 479 if func: |
| 480 cherrypy.serving.request.handler = LateParamPageHandler(func) |
| 481 else: |
| 482 cherrypy.serving.request.handler = cherrypy.NotFound() |
| 483 |
| 484 def find_handler(self, path_info): |
| 485 """Find the right page handler, and set request.config.""" |
| 486 import routes |
| 487 |
| 488 request = cherrypy.serving.request |
| 489 |
| 490 config = routes.request_config() |
| 491 config.mapper = self.mapper |
| 492 if hasattr(request, 'wsgi_environ'): |
| 493 config.environ = request.wsgi_environ |
| 494 config.host = request.headers.get('Host', None) |
| 495 config.protocol = request.scheme |
| 496 config.redirect = self.redirect |
| 497 |
| 498 result = self.mapper.match(path_info) |
| 499 |
| 500 config.mapper_dict = result |
| 501 params = {} |
| 502 if result: |
| 503 params = result.copy() |
| 504 if not self.full_result: |
| 505 params.pop('controller', None) |
| 506 params.pop('action', None) |
| 507 request.params.update(params) |
| 508 |
| 509 # Get config for the root object/path. |
| 510 request.config = base = cherrypy.config.copy() |
| 511 curpath = "" |
| 512 |
| 513 def merge(nodeconf): |
| 514 if 'tools.staticdir.dir' in nodeconf: |
| 515 nodeconf['tools.staticdir.section'] = curpath or "/" |
| 516 base.update(nodeconf) |
| 517 |
| 518 app = request.app |
| 519 root = app.root |
| 520 if hasattr(root, "_cp_config"): |
| 521 merge(root._cp_config) |
| 522 if "/" in app.config: |
| 523 merge(app.config["/"]) |
| 524 |
| 525 # Mix in values from app.config. |
| 526 atoms = [x for x in path_info.split("/") if x] |
| 527 if atoms: |
| 528 last = atoms.pop() |
| 529 else: |
| 530 last = None |
| 531 for atom in atoms: |
| 532 curpath = "/".join((curpath, atom)) |
| 533 if curpath in app.config: |
| 534 merge(app.config[curpath]) |
| 535 |
| 536 handler = None |
| 537 if result: |
| 538 controller = result.get('controller') |
| 539 controller = self.controllers.get(controller, controller) |
| 540 if controller: |
| 541 if isinstance(controller, classtype): |
| 542 controller = controller() |
| 543 # Get config from the controller. |
| 544 if hasattr(controller, "_cp_config"): |
| 545 merge(controller._cp_config) |
| 546 |
| 547 action = result.get('action') |
| 548 if action is not None: |
| 549 handler = getattr(controller, action, None) |
| 550 # Get config from the handler |
| 551 if hasattr(handler, "_cp_config"): |
| 552 merge(handler._cp_config) |
| 553 else: |
| 554 handler = controller |
| 555 |
| 556 # Do the last path atom here so it can |
| 557 # override the controller's _cp_config. |
| 558 if last: |
| 559 curpath = "/".join((curpath, last)) |
| 560 if curpath in app.config: |
| 561 merge(app.config[curpath]) |
| 562 |
| 563 return handler |
| 564 |
| 565 |
| 566 def XMLRPCDispatcher(next_dispatcher=Dispatcher()): |
| 567 from cherrypy.lib import xmlrpcutil |
| 568 def xmlrpc_dispatch(path_info): |
| 569 path_info = xmlrpcutil.patched_path(path_info) |
| 570 return next_dispatcher(path_info) |
| 571 return xmlrpc_dispatch |
| 572 |
| 573 |
| 574 def VirtualHost(next_dispatcher=Dispatcher(), use_x_forwarded_host=True, **domai
ns): |
| 575 """ |
| 576 Select a different handler based on the Host header. |
| 577 |
| 578 This can be useful when running multiple sites within one CP server. |
| 579 It allows several domains to point to different parts of a single |
| 580 website structure. For example:: |
| 581 |
| 582 http://www.domain.example -> root |
| 583 http://www.domain2.example -> root/domain2/ |
| 584 http://www.domain2.example:443 -> root/secure |
| 585 |
| 586 can be accomplished via the following config:: |
| 587 |
| 588 [/] |
| 589 request.dispatch = cherrypy.dispatch.VirtualHost( |
| 590 **{'www.domain2.example': '/domain2', |
| 591 'www.domain2.example:443': '/secure', |
| 592 }) |
| 593 |
| 594 next_dispatcher |
| 595 The next dispatcher object in the dispatch chain. |
| 596 The VirtualHost dispatcher adds a prefix to the URL and calls |
| 597 another dispatcher. Defaults to cherrypy.dispatch.Dispatcher(). |
| 598 |
| 599 use_x_forwarded_host |
| 600 If True (the default), any "X-Forwarded-Host" |
| 601 request header will be used instead of the "Host" header. This |
| 602 is commonly added by HTTP servers (such as Apache) when proxying. |
| 603 |
| 604 ``**domains`` |
| 605 A dict of {host header value: virtual prefix} pairs. |
| 606 The incoming "Host" request header is looked up in this dict, |
| 607 and, if a match is found, the corresponding "virtual prefix" |
| 608 value will be prepended to the URL path before calling the |
| 609 next dispatcher. Note that you often need separate entries |
| 610 for "example.com" and "www.example.com". In addition, "Host" |
| 611 headers may contain the port number. |
| 612 """ |
| 613 from cherrypy.lib import httputil |
| 614 def vhost_dispatch(path_info): |
| 615 request = cherrypy.serving.request |
| 616 header = request.headers.get |
| 617 |
| 618 domain = header('Host', '') |
| 619 if use_x_forwarded_host: |
| 620 domain = header("X-Forwarded-Host", domain) |
| 621 |
| 622 prefix = domains.get(domain, "") |
| 623 if prefix: |
| 624 path_info = httputil.urljoin(prefix, path_info) |
| 625 |
| 626 result = next_dispatcher(path_info) |
| 627 |
| 628 # Touch up staticdir config. See http://www.cherrypy.org/ticket/614. |
| 629 section = request.config.get('tools.staticdir.section') |
| 630 if section: |
| 631 section = section[len(prefix):] |
| 632 request.config['tools.staticdir.section'] = section |
| 633 |
| 634 return result |
| 635 return vhost_dispatch |
| 636 |
OLD | NEW |