/circuits/web/dispatchers/dispatcher.py

https://bitbucket.org/prologic/circuits/ · Python · 147 lines · 101 code · 36 blank · 10 comment · 36 complexity · 59075dd5fff9aba324585ca9d5f337d1 MD5 · raw file

  1. # Module: dispatcher
  2. # Date: 13th September 2007
  3. # Author: James Mills, prologic at shortcircuit dot net dot au
  4. """Dispatcher
  5. This module implements a basic URL to Channel dispatcher.
  6. This is the default dispatcher used by circuits.web
  7. """
  8. try:
  9. from urllib import quote, unquote
  10. except ImportError:
  11. from urllib.parse import quote, unquote # NOQA
  12. from circuits.six import text_type
  13. from circuits import handler, BaseComponent, Event
  14. from circuits.web.utils import parse_qs
  15. from circuits.web.events import response
  16. from circuits.web.errors import httperror
  17. from circuits.web.processors import process
  18. from circuits.web.controllers import BaseController
  19. def resolve_path(paths, parts):
  20. def rebuild_path(url_parts):
  21. return '/%s' % '/'.join(url_parts)
  22. left_over = []
  23. while parts:
  24. if rebuild_path(parts) in paths:
  25. yield rebuild_path(parts), left_over
  26. left_over.insert(0, parts.pop())
  27. if '/' in paths:
  28. yield '/', left_over
  29. def resolve_methods(parts):
  30. if parts:
  31. method = parts[0]
  32. vpath = parts[1:]
  33. yield method, vpath
  34. yield 'index', parts
  35. def find_handlers(req, paths):
  36. def get_handlers(path, method):
  37. component = paths[path]
  38. return component._handlers.get(method, None)
  39. def accepts_vpath(handlers, vpath):
  40. args_no = len(vpath)
  41. return all(
  42. len(h.args) == args_no or h.varargs or (
  43. h.defaults is not None and args_no <= len(h.defaults)
  44. )
  45. for h in handlers
  46. )
  47. # Split /hello/world to ['hello', 'world']
  48. starting_parts = [x for x in req.path.strip("/").split("/") if x]
  49. for path, parts in resolve_path(paths, starting_parts):
  50. handlers = get_handlers(path, req.method)
  51. if handlers:
  52. return handlers, req.method, path, parts
  53. for method, vpath in resolve_methods(parts):
  54. handlers = get_handlers(path, method)
  55. if handlers and (not vpath or accepts_vpath(handlers, vpath)):
  56. req.index = (method == 'index')
  57. return handlers, method, path, vpath
  58. else:
  59. method, vpath = "index", [method] + vpath
  60. handlers = get_handlers(path, method)
  61. if handlers and (not vpath or accepts_vpath(handlers, vpath)):
  62. req.index = True
  63. return handlers, method, path, vpath
  64. return [], None, None, None
  65. class Dispatcher(BaseComponent):
  66. channel = "web"
  67. def __init__(self, **kwargs):
  68. super(Dispatcher, self).__init__(**kwargs)
  69. self.paths = dict()
  70. @handler("registered", channel="*")
  71. def _on_registered(self, component, manager):
  72. if (isinstance(component, BaseController) and component.channel not
  73. in self.paths):
  74. self.paths[component.channel] = component
  75. @handler("unregistered", channel="*")
  76. def _on_unregistered(self, component, manager):
  77. if (isinstance(component, BaseController) and component.channel in
  78. self.paths):
  79. del self.paths[component.channel]
  80. @handler("request", priority=0.1)
  81. def _on_request(self, event, req, res, peer_cert=None):
  82. if peer_cert:
  83. event.peer_cert = peer_cert
  84. handlers, name, channel, vpath = find_handlers(req, self.paths)
  85. if name is not None and channel is not None:
  86. event.kwargs = parse_qs(req.qs)
  87. process(req, event.kwargs)
  88. if vpath:
  89. event.args += tuple(vpath)
  90. if isinstance(name, text_type):
  91. name = str(name)
  92. return self.fire(
  93. Event.create(
  94. name, *event.args, **event.kwargs
  95. ),
  96. channel
  97. )
  98. @handler("request_value_changed")
  99. def _on_request_value_changed(self, value):
  100. if value.handled:
  101. return
  102. req, res = value.event.args[:2]
  103. if value.result and not value.errors:
  104. res.body = value.value
  105. self.fire(response(res))
  106. elif value.promise:
  107. value.event.notify = True
  108. else:
  109. # Errors are handled by the ``HTTP`` Protocol Component
  110. return