PageRenderTime 56ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 1ms

/external/chromium-trace/trace-viewer/third_party/pywebsocket/src/mod_pywebsocket/dispatch.py

https://gitlab.com/brian0218/rk3066_r-box_android4.2.2_sdk
Python | 387 lines | 218 code | 49 blank | 120 comment | 42 complexity | 2442c6ed7569fd0baf3574f1a1747e59 MD5 | raw file
  1. # Copyright 2012, Google Inc.
  2. # All rights reserved.
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions are
  6. # met:
  7. #
  8. # * Redistributions of source code must retain the above copyright
  9. # notice, this list of conditions and the following disclaimer.
  10. # * Redistributions in binary form must reproduce the above
  11. # copyright notice, this list of conditions and the following disclaimer
  12. # in the documentation and/or other materials provided with the
  13. # distribution.
  14. # * Neither the name of Google Inc. nor the names of its
  15. # contributors may be used to endorse or promote products derived from
  16. # this software without specific prior written permission.
  17. #
  18. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. """Dispatch WebSocket request.
  30. """
  31. import logging
  32. import os
  33. import re
  34. from mod_pywebsocket import common
  35. from mod_pywebsocket import handshake
  36. from mod_pywebsocket import msgutil
  37. from mod_pywebsocket import mux
  38. from mod_pywebsocket import stream
  39. from mod_pywebsocket import util
  40. _SOURCE_PATH_PATTERN = re.compile(r'(?i)_wsh\.py$')
  41. _SOURCE_SUFFIX = '_wsh.py'
  42. _DO_EXTRA_HANDSHAKE_HANDLER_NAME = 'web_socket_do_extra_handshake'
  43. _TRANSFER_DATA_HANDLER_NAME = 'web_socket_transfer_data'
  44. _PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME = (
  45. 'web_socket_passive_closing_handshake')
  46. class DispatchException(Exception):
  47. """Exception in dispatching WebSocket request."""
  48. def __init__(self, name, status=common.HTTP_STATUS_NOT_FOUND):
  49. super(DispatchException, self).__init__(name)
  50. self.status = status
  51. def _default_passive_closing_handshake_handler(request):
  52. """Default web_socket_passive_closing_handshake handler."""
  53. return common.STATUS_NORMAL_CLOSURE, ''
  54. def _normalize_path(path):
  55. """Normalize path.
  56. Args:
  57. path: the path to normalize.
  58. Path is converted to the absolute path.
  59. The input path can use either '\\' or '/' as the separator.
  60. The normalized path always uses '/' regardless of the platform.
  61. """
  62. path = path.replace('\\', os.path.sep)
  63. path = os.path.realpath(path)
  64. path = path.replace('\\', '/')
  65. return path
  66. def _create_path_to_resource_converter(base_dir):
  67. """Returns a function that converts the path of a WebSocket handler source
  68. file to a resource string by removing the path to the base directory from
  69. its head, removing _SOURCE_SUFFIX from its tail, and replacing path
  70. separators in it with '/'.
  71. Args:
  72. base_dir: the path to the base directory.
  73. """
  74. base_dir = _normalize_path(base_dir)
  75. base_len = len(base_dir)
  76. suffix_len = len(_SOURCE_SUFFIX)
  77. def converter(path):
  78. if not path.endswith(_SOURCE_SUFFIX):
  79. return None
  80. # _normalize_path must not be used because resolving symlink breaks
  81. # following path check.
  82. path = path.replace('\\', '/')
  83. if not path.startswith(base_dir):
  84. return None
  85. return path[base_len:-suffix_len]
  86. return converter
  87. def _enumerate_handler_file_paths(directory):
  88. """Returns a generator that enumerates WebSocket Handler source file names
  89. in the given directory.
  90. """
  91. for root, unused_dirs, files in os.walk(directory):
  92. for base in files:
  93. path = os.path.join(root, base)
  94. if _SOURCE_PATH_PATTERN.search(path):
  95. yield path
  96. class _HandlerSuite(object):
  97. """A handler suite holder class."""
  98. def __init__(self, do_extra_handshake, transfer_data,
  99. passive_closing_handshake):
  100. self.do_extra_handshake = do_extra_handshake
  101. self.transfer_data = transfer_data
  102. self.passive_closing_handshake = passive_closing_handshake
  103. def _source_handler_file(handler_definition):
  104. """Source a handler definition string.
  105. Args:
  106. handler_definition: a string containing Python statements that define
  107. handler functions.
  108. """
  109. global_dic = {}
  110. try:
  111. exec handler_definition in global_dic
  112. except Exception:
  113. raise DispatchException('Error in sourcing handler:' +
  114. util.get_stack_trace())
  115. passive_closing_handshake_handler = None
  116. try:
  117. passive_closing_handshake_handler = _extract_handler(
  118. global_dic, _PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME)
  119. except Exception:
  120. passive_closing_handshake_handler = (
  121. _default_passive_closing_handshake_handler)
  122. return _HandlerSuite(
  123. _extract_handler(global_dic, _DO_EXTRA_HANDSHAKE_HANDLER_NAME),
  124. _extract_handler(global_dic, _TRANSFER_DATA_HANDLER_NAME),
  125. passive_closing_handshake_handler)
  126. def _extract_handler(dic, name):
  127. """Extracts a callable with the specified name from the given dictionary
  128. dic.
  129. """
  130. if name not in dic:
  131. raise DispatchException('%s is not defined.' % name)
  132. handler = dic[name]
  133. if not callable(handler):
  134. raise DispatchException('%s is not callable.' % name)
  135. return handler
  136. class Dispatcher(object):
  137. """Dispatches WebSocket requests.
  138. This class maintains a map from resource name to handlers.
  139. """
  140. def __init__(
  141. self, root_dir, scan_dir=None,
  142. allow_handlers_outside_root_dir=True):
  143. """Construct an instance.
  144. Args:
  145. root_dir: The directory where handler definition files are
  146. placed.
  147. scan_dir: The directory where handler definition files are
  148. searched. scan_dir must be a directory under root_dir,
  149. including root_dir itself. If scan_dir is None,
  150. root_dir is used as scan_dir. scan_dir can be useful
  151. in saving scan time when root_dir contains many
  152. subdirectories.
  153. allow_handlers_outside_root_dir: Scans handler files even if their
  154. canonical path is not under root_dir.
  155. """
  156. self._logger = util.get_class_logger(self)
  157. self._handler_suite_map = {}
  158. self._source_warnings = []
  159. if scan_dir is None:
  160. scan_dir = root_dir
  161. if not os.path.realpath(scan_dir).startswith(
  162. os.path.realpath(root_dir)):
  163. raise DispatchException('scan_dir:%s must be a directory under '
  164. 'root_dir:%s.' % (scan_dir, root_dir))
  165. self._source_handler_files_in_dir(
  166. root_dir, scan_dir, allow_handlers_outside_root_dir)
  167. def add_resource_path_alias(self,
  168. alias_resource_path, existing_resource_path):
  169. """Add resource path alias.
  170. Once added, request to alias_resource_path would be handled by
  171. handler registered for existing_resource_path.
  172. Args:
  173. alias_resource_path: alias resource path
  174. existing_resource_path: existing resource path
  175. """
  176. try:
  177. handler_suite = self._handler_suite_map[existing_resource_path]
  178. self._handler_suite_map[alias_resource_path] = handler_suite
  179. except KeyError:
  180. raise DispatchException('No handler for: %r' %
  181. existing_resource_path)
  182. def source_warnings(self):
  183. """Return warnings in sourcing handlers."""
  184. return self._source_warnings
  185. def do_extra_handshake(self, request):
  186. """Do extra checking in WebSocket handshake.
  187. Select a handler based on request.uri and call its
  188. web_socket_do_extra_handshake function.
  189. Args:
  190. request: mod_python request.
  191. Raises:
  192. DispatchException: when handler was not found
  193. AbortedByUserException: when user handler abort connection
  194. HandshakeException: when opening handshake failed
  195. """
  196. handler_suite = self.get_handler_suite(request.ws_resource)
  197. if handler_suite is None:
  198. raise DispatchException('No handler for: %r' % request.ws_resource)
  199. do_extra_handshake_ = handler_suite.do_extra_handshake
  200. try:
  201. do_extra_handshake_(request)
  202. except handshake.AbortedByUserException, e:
  203. raise
  204. except Exception, e:
  205. util.prepend_message_to_exception(
  206. '%s raised exception for %s: ' % (
  207. _DO_EXTRA_HANDSHAKE_HANDLER_NAME,
  208. request.ws_resource),
  209. e)
  210. raise handshake.HandshakeException(e, common.HTTP_STATUS_FORBIDDEN)
  211. def transfer_data(self, request):
  212. """Let a handler transfer_data with a WebSocket client.
  213. Select a handler based on request.ws_resource and call its
  214. web_socket_transfer_data function.
  215. Args:
  216. request: mod_python request.
  217. Raises:
  218. DispatchException: when handler was not found
  219. AbortedByUserException: when user handler abort connection
  220. """
  221. # TODO(tyoshino): Terminate underlying TCP connection if possible.
  222. try:
  223. if mux.use_mux(request):
  224. mux.start(request, self)
  225. else:
  226. handler_suite = self.get_handler_suite(request.ws_resource)
  227. if handler_suite is None:
  228. raise DispatchException('No handler for: %r' %
  229. request.ws_resource)
  230. transfer_data_ = handler_suite.transfer_data
  231. transfer_data_(request)
  232. if not request.server_terminated:
  233. request.ws_stream.close_connection()
  234. # Catch non-critical exceptions the handler didn't handle.
  235. except handshake.AbortedByUserException, e:
  236. self._logger.debug('%s', e)
  237. raise
  238. except msgutil.BadOperationException, e:
  239. self._logger.debug('%s', e)
  240. request.ws_stream.close_connection(common.STATUS_ABNORMAL_CLOSURE)
  241. except msgutil.InvalidFrameException, e:
  242. # InvalidFrameException must be caught before
  243. # ConnectionTerminatedException that catches InvalidFrameException.
  244. self._logger.debug('%s', e)
  245. request.ws_stream.close_connection(common.STATUS_PROTOCOL_ERROR)
  246. except msgutil.UnsupportedFrameException, e:
  247. self._logger.debug('%s', e)
  248. request.ws_stream.close_connection(common.STATUS_UNSUPPORTED_DATA)
  249. except stream.InvalidUTF8Exception, e:
  250. self._logger.debug('%s', e)
  251. request.ws_stream.close_connection(
  252. common.STATUS_INVALID_FRAME_PAYLOAD_DATA)
  253. except msgutil.ConnectionTerminatedException, e:
  254. self._logger.debug('%s', e)
  255. except Exception, e:
  256. util.prepend_message_to_exception(
  257. '%s raised exception for %s: ' % (
  258. _TRANSFER_DATA_HANDLER_NAME, request.ws_resource),
  259. e)
  260. raise
  261. def passive_closing_handshake(self, request):
  262. """Prepare code and reason for responding client initiated closing
  263. handshake.
  264. """
  265. handler_suite = self.get_handler_suite(request.ws_resource)
  266. if handler_suite is None:
  267. return _default_passive_closing_handshake_handler(request)
  268. return handler_suite.passive_closing_handshake(request)
  269. def get_handler_suite(self, resource):
  270. """Retrieves two handlers (one for extra handshake processing, and one
  271. for data transfer) for the given request as a HandlerSuite object.
  272. """
  273. fragment = None
  274. if '#' in resource:
  275. resource, fragment = resource.split('#', 1)
  276. if '?' in resource:
  277. resource = resource.split('?', 1)[0]
  278. handler_suite = self._handler_suite_map.get(resource)
  279. if handler_suite and fragment:
  280. raise DispatchException('Fragment identifiers MUST NOT be used on '
  281. 'WebSocket URIs',
  282. common.HTTP_STATUS_BAD_REQUEST)
  283. return handler_suite
  284. def _source_handler_files_in_dir(
  285. self, root_dir, scan_dir, allow_handlers_outside_root_dir):
  286. """Source all the handler source files in the scan_dir directory.
  287. The resource path is determined relative to root_dir.
  288. """
  289. # We build a map from resource to handler code assuming that there's
  290. # only one path from root_dir to scan_dir and it can be obtained by
  291. # comparing realpath of them.
  292. # Here we cannot use abspath. See
  293. # https://bugs.webkit.org/show_bug.cgi?id=31603
  294. convert = _create_path_to_resource_converter(root_dir)
  295. scan_realpath = os.path.realpath(scan_dir)
  296. root_realpath = os.path.realpath(root_dir)
  297. for path in _enumerate_handler_file_paths(scan_realpath):
  298. if (not allow_handlers_outside_root_dir and
  299. (not os.path.realpath(path).startswith(root_realpath))):
  300. self._logger.debug(
  301. 'Canonical path of %s is not under root directory' %
  302. path)
  303. continue
  304. try:
  305. handler_suite = _source_handler_file(open(path).read())
  306. except DispatchException, e:
  307. self._source_warnings.append('%s: %s' % (path, e))
  308. continue
  309. resource = convert(path)
  310. if resource is None:
  311. self._logger.debug(
  312. 'Path to resource conversion on %s failed' % path)
  313. else:
  314. self._handler_suite_map[convert(path)] = handler_suite
  315. # vi:sts=4 sw=4 et