You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

websocket.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. ###############################################################################
  2. #
  3. # The MIT License (MIT)
  4. #
  5. # Copyright (c) Crossbar.io Technologies GmbH
  6. #
  7. # Permission is hereby granted, free of charge, to any person obtaining a copy
  8. # of this software and associated documentation files (the "Software"), to deal
  9. # in the Software without restriction, including without limitation the rights
  10. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. # copies of the Software, and to permit persons to whom the Software is
  12. # furnished to do so, subject to the following conditions:
  13. #
  14. # The above copyright notice and this permission notice shall be included in
  15. # all copies or substantial portions of the Software.
  16. #
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. # THE SOFTWARE.
  24. #
  25. ###############################################################################
  26. from __future__ import absolute_import
  27. import traceback
  28. from autobahn.websocket import protocol
  29. from autobahn.websocket.types import ConnectionDeny
  30. from autobahn.wamp.interfaces import ITransport
  31. from autobahn.wamp.exception import ProtocolError, SerializationError, TransportLost
  32. __all__ = ('WampWebSocketServerProtocol',
  33. 'WampWebSocketClientProtocol',
  34. 'WampWebSocketServerFactory',
  35. 'WampWebSocketClientFactory')
  36. class WampWebSocketProtocol(object):
  37. """
  38. Base class for WAMP-over-WebSocket transport mixins.
  39. """
  40. _session = None # default; self.session is set in onOpen
  41. def _bailout(self, code, reason=None):
  42. self.log.debug('Failing WAMP-over-WebSocket transport: code={code}, reason="{reason}"', code=code, reason=reason)
  43. self._fail_connection(code, reason)
  44. def onOpen(self):
  45. """
  46. Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onOpen`
  47. """
  48. # WebSocket connection established. Now let the user WAMP session factory
  49. # create a new WAMP session and fire off session open callback.
  50. try:
  51. self._session = self.factory._factory()
  52. self._session.onOpen(self)
  53. except Exception as e:
  54. self.log.critical("{tb}", tb=traceback.format_exc())
  55. reason = u'WAMP Internal Error ({0})'.format(e)
  56. self._bailout(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_INTERNAL_ERROR, reason=reason)
  57. def onClose(self, wasClean, code, reason):
  58. """
  59. Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onClose`
  60. """
  61. # WAMP session might never have been established in the first place .. guard this!
  62. self._onclose_reason = reason
  63. if self._session is not None:
  64. # WebSocket connection lost - fire off the WAMP
  65. # session close callback
  66. # noinspection PyBroadException
  67. try:
  68. self.log.debug('WAMP-over-WebSocket transport lost: wasClean={wasClean}, code={code}, reason="{reason}"', wasClean=wasClean, code=code, reason=reason)
  69. self._session.onClose(wasClean)
  70. except Exception:
  71. self.log.critical("{tb}", tb=traceback.format_exc())
  72. self._session = None
  73. def onMessage(self, payload, isBinary):
  74. """
  75. Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onMessage`
  76. """
  77. try:
  78. for msg in self._serializer.unserialize(payload, isBinary):
  79. self.log.trace(
  80. "WAMP RECV: message={message}, session={session}, authid={authid}",
  81. authid=self._session._authid,
  82. session=self._session._session_id,
  83. message=msg,
  84. )
  85. self._session.onMessage(msg)
  86. except ProtocolError as e:
  87. self.log.critical("{tb}", tb=traceback.format_exc())
  88. reason = u'WAMP Protocol Error ({0})'.format(e)
  89. self._bailout(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_PROTOCOL_ERROR, reason=reason)
  90. except Exception as e:
  91. self.log.critical("{tb}", tb=traceback.format_exc())
  92. reason = u'WAMP Internal Error ({0})'.format(e)
  93. self._bailout(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_INTERNAL_ERROR, reason=reason)
  94. def send(self, msg):
  95. """
  96. Implements :func:`autobahn.wamp.interfaces.ITransport.send`
  97. """
  98. if self.isOpen():
  99. try:
  100. self.log.trace(
  101. "WAMP SEND: message={message}, session={session}, authid={authid}",
  102. authid=self._session._authid,
  103. session=self._session._session_id,
  104. message=msg,
  105. )
  106. payload, isBinary = self._serializer.serialize(msg)
  107. except Exception as e:
  108. self.log.error("WAMP message serialization error: {}".format(e))
  109. # all exceptions raised from above should be serialization errors ..
  110. raise SerializationError(u"WAMP message serialization error: {0}".format(e))
  111. else:
  112. self.sendMessage(payload, isBinary)
  113. else:
  114. raise TransportLost()
  115. def isOpen(self):
  116. """
  117. Implements :func:`autobahn.wamp.interfaces.ITransport.isOpen`
  118. """
  119. return self._session is not None
  120. def close(self):
  121. """
  122. Implements :func:`autobahn.wamp.interfaces.ITransport.close`
  123. """
  124. if self.isOpen():
  125. self.sendClose(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL)
  126. else:
  127. raise TransportLost()
  128. def abort(self):
  129. """
  130. Implements :func:`autobahn.wamp.interfaces.ITransport.abort`
  131. """
  132. if self.isOpen():
  133. self._bailout(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_GOING_AWAY)
  134. else:
  135. raise TransportLost()
  136. ITransport.register(WampWebSocketProtocol)
  137. def parseSubprotocolIdentifier(subprotocol):
  138. try:
  139. s = subprotocol.split(u'.')
  140. if s[0] != u'wamp':
  141. raise Exception(u'WAMP WebSocket subprotocol identifier must start with "wamp", not "{}"'.format(s[0]))
  142. version = int(s[1])
  143. serializerId = u'.'.join(s[2:])
  144. return version, serializerId
  145. except:
  146. return None, None
  147. class WampWebSocketServerProtocol(WampWebSocketProtocol):
  148. """
  149. Mixin for WAMP-over-WebSocket server transports.
  150. """
  151. STRICT_PROTOCOL_NEGOTIATION = True
  152. def onConnect(self, request):
  153. """
  154. Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onConnect`
  155. """
  156. headers = {}
  157. for subprotocol in request.protocols:
  158. version, serializerId = parseSubprotocolIdentifier(subprotocol)
  159. if version == 2 and serializerId in self.factory._serializers.keys():
  160. self._serializer = self.factory._serializers[serializerId]
  161. return subprotocol, headers
  162. if self.STRICT_PROTOCOL_NEGOTIATION:
  163. raise ConnectionDeny(ConnectionDeny.BAD_REQUEST, u'This server only speaks WebSocket subprotocols {}'.format(u', '.join(self.factory.protocols)))
  164. else:
  165. # assume wamp.2.json
  166. self._serializer = self.factory._serializers[u'json']
  167. return None, headers
  168. class WampWebSocketClientProtocol(WampWebSocketProtocol):
  169. """
  170. Mixin for WAMP-over-WebSocket client transports.
  171. """
  172. STRICT_PROTOCOL_NEGOTIATION = True
  173. def onConnect(self, response):
  174. """
  175. Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onConnect`
  176. """
  177. if response.protocol not in self.factory.protocols:
  178. if self.STRICT_PROTOCOL_NEGOTIATION:
  179. raise Exception(u'The server does not speak any of the WebSocket subprotocols {} we requested.'.format(u', '.join(self.factory.protocols)))
  180. else:
  181. # assume wamp.2.json
  182. serializerId = u'json'
  183. else:
  184. version, serializerId = parseSubprotocolIdentifier(response.protocol)
  185. self._serializer = self.factory._serializers[serializerId]
  186. class WampWebSocketFactory(object):
  187. """
  188. Base class for WAMP-over-WebSocket transport factory mixins.
  189. """
  190. def __init__(self, factory, serializers=None):
  191. """
  192. Ctor.
  193. :param factory: A callable that produces instances that implement
  194. :class:`autobahn.wamp.interfaces.ITransportHandler`
  195. :type factory: callable
  196. :param serializers: A list of WAMP serializers to use (or None for default
  197. serializers). Serializers must implement
  198. :class:`autobahn.wamp.interfaces.ISerializer`.
  199. :type serializers: list
  200. """
  201. if callable(factory):
  202. self._factory = factory
  203. else:
  204. self._factory = lambda: factory
  205. if serializers is None:
  206. serializers = []
  207. # try CBOR WAMP serializer
  208. try:
  209. from autobahn.wamp.serializer import CBORSerializer
  210. serializers.append(CBORSerializer(batched=True))
  211. serializers.append(CBORSerializer())
  212. except ImportError:
  213. pass
  214. # try MsgPack WAMP serializer
  215. try:
  216. from autobahn.wamp.serializer import MsgPackSerializer
  217. serializers.append(MsgPackSerializer(batched=True))
  218. serializers.append(MsgPackSerializer())
  219. except ImportError:
  220. pass
  221. # try UBJSON WAMP serializer
  222. try:
  223. from autobahn.wamp.serializer import UBJSONSerializer
  224. serializers.append(UBJSONSerializer(batched=True))
  225. serializers.append(UBJSONSerializer())
  226. except ImportError:
  227. pass
  228. # try JSON WAMP serializer
  229. try:
  230. from autobahn.wamp.serializer import JsonSerializer
  231. serializers.append(JsonSerializer(batched=True))
  232. serializers.append(JsonSerializer())
  233. except ImportError:
  234. pass
  235. if not serializers:
  236. raise Exception(u'Could not import any WAMP serializer')
  237. self._serializers = {}
  238. for ser in serializers:
  239. self._serializers[ser.SERIALIZER_ID] = ser
  240. self._protocols = [u'wamp.2.{}'.format(ser.SERIALIZER_ID) for ser in serializers]
  241. class WampWebSocketServerFactory(WampWebSocketFactory):
  242. """
  243. Mixin for WAMP-over-WebSocket server transport factories.
  244. """
  245. class WampWebSocketClientFactory(WampWebSocketFactory):
  246. """
  247. Mixin for WAMP-over-WebSocket client transport factories.
  248. """