Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
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 12KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. ###############################################################################
  2. #
  3. # The MIT License (MIT)
  4. #
  5. # Copyright (c) typedef int 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. import asyncio
  27. from asyncio import iscoroutine
  28. from asyncio import Future
  29. from collections import deque
  30. from typing import Optional
  31. import txaio
  32. txaio.use_asyncio() # noqa
  33. from autobahn.util import public, hltype
  34. from autobahn.asyncio.util import create_transport_details, transport_channel_id
  35. from autobahn.wamp import websocket
  36. from autobahn.websocket import protocol
  37. __all__ = (
  38. 'WebSocketServerProtocol',
  39. 'WebSocketClientProtocol',
  40. 'WebSocketServerFactory',
  41. 'WebSocketClientFactory',
  42. 'WampWebSocketServerProtocol',
  43. 'WampWebSocketClientProtocol',
  44. 'WampWebSocketServerFactory',
  45. 'WampWebSocketClientFactory',
  46. )
  47. def yields(value):
  48. """
  49. Returns ``True`` iff the value yields.
  50. .. seealso:: http://stackoverflow.com/questions/20730248/maybedeferred-analog-with-asyncio
  51. """
  52. return isinstance(value, Future) or iscoroutine(value)
  53. class WebSocketAdapterProtocol(asyncio.Protocol):
  54. """
  55. Adapter class for asyncio-based WebSocket client and server protocols.
  56. """
  57. log = txaio.make_logger()
  58. peer: Optional[str] = None
  59. is_server: Optional[bool] = None
  60. def connection_made(self, transport):
  61. # asyncio networking framework entry point, called by asyncio
  62. # when the connection is established (either a client or a server)
  63. self.log.debug('{func}(transport={transport})', func=hltype(self.connection_made),
  64. transport=transport)
  65. self.transport = transport
  66. # determine preliminary transport details (what is know at this point)
  67. self._transport_details = create_transport_details(self.transport, self.is_server)
  68. # backward compatibility
  69. self.peer = self._transport_details.peer
  70. self.receive_queue = deque()
  71. self._consume()
  72. self._connectionMade()
  73. def connection_lost(self, exc):
  74. self._connectionLost(exc)
  75. # according to asyncio docs, connection_lost(None) is called
  76. # if something else called transport.close()
  77. if exc is not None:
  78. self.transport.close()
  79. self.transport = None
  80. def _consume(self):
  81. self.waiter = Future(loop=self.factory.loop or txaio.config.loop)
  82. def process(_):
  83. while self.receive_queue:
  84. data = self.receive_queue.popleft()
  85. if self.transport:
  86. self._dataReceived(data)
  87. self._consume()
  88. self.waiter.add_done_callback(process)
  89. def data_received(self, data):
  90. self.receive_queue.append(data)
  91. if not self.waiter.done():
  92. self.waiter.set_result(None)
  93. def _closeConnection(self, abort=False):
  94. if abort and hasattr(self.transport, 'abort'):
  95. self.transport.abort()
  96. else:
  97. self.transport.close()
  98. def _onOpen(self):
  99. if self._transport_details.is_secure:
  100. # now that the TLS opening handshake is complete, the actual TLS channel ID
  101. # will be available. make sure to set it!
  102. channel_id = {
  103. 'tls-unique': transport_channel_id(self.transport, self._transport_details.is_server, 'tls-unique'),
  104. }
  105. self._transport_details.channel_id = channel_id
  106. res = self.onOpen()
  107. if yields(res):
  108. asyncio.ensure_future(res)
  109. def _onMessageBegin(self, isBinary):
  110. res = self.onMessageBegin(isBinary)
  111. if yields(res):
  112. asyncio.ensure_future(res)
  113. def _onMessageFrameBegin(self, length):
  114. res = self.onMessageFrameBegin(length)
  115. if yields(res):
  116. asyncio.ensure_future(res)
  117. def _onMessageFrameData(self, payload):
  118. res = self.onMessageFrameData(payload)
  119. if yields(res):
  120. asyncio.ensure_future(res)
  121. def _onMessageFrameEnd(self):
  122. res = self.onMessageFrameEnd()
  123. if yields(res):
  124. asyncio.ensure_future(res)
  125. def _onMessageFrame(self, payload):
  126. res = self.onMessageFrame(payload)
  127. if yields(res):
  128. asyncio.ensure_future(res)
  129. def _onMessageEnd(self):
  130. res = self.onMessageEnd()
  131. if yields(res):
  132. asyncio.ensure_future(res)
  133. def _onMessage(self, payload, isBinary):
  134. res = self.onMessage(payload, isBinary)
  135. if yields(res):
  136. asyncio.ensure_future(res)
  137. def _onPing(self, payload):
  138. res = self.onPing(payload)
  139. if yields(res):
  140. asyncio.ensure_future(res)
  141. def _onPong(self, payload):
  142. res = self.onPong(payload)
  143. if yields(res):
  144. asyncio.ensure_future(res)
  145. def _onClose(self, wasClean, code, reason):
  146. res = self.onClose(wasClean, code, reason)
  147. if yields(res):
  148. asyncio.ensure_future(res)
  149. def registerProducer(self, producer, streaming):
  150. raise Exception("not implemented")
  151. def unregisterProducer(self):
  152. # note that generic websocket/protocol.py code calls
  153. # .unregisterProducer whenever we dropConnection -- that's
  154. # correct behavior on Twisted so either we'd have to
  155. # try/except there, or special-case Twisted, ..or just make
  156. # this "not an error"
  157. pass
  158. @public
  159. class WebSocketServerProtocol(WebSocketAdapterProtocol, protocol.WebSocketServerProtocol):
  160. """
  161. Base class for asyncio-based WebSocket server protocols.
  162. Implements:
  163. * :class:`autobahn.websocket.interfaces.IWebSocketChannel`
  164. """
  165. log = txaio.make_logger()
  166. @public
  167. class WebSocketClientProtocol(WebSocketAdapterProtocol, protocol.WebSocketClientProtocol):
  168. """
  169. Base class for asyncio-based WebSocket client protocols.
  170. Implements:
  171. * :class:`autobahn.websocket.interfaces.IWebSocketChannel`
  172. """
  173. log = txaio.make_logger()
  174. def _onConnect(self, response):
  175. res = self.onConnect(response)
  176. self.log.debug('{func}: {res}', func=hltype(self._onConnect), res=res)
  177. if yields(res):
  178. asyncio.ensure_future(res)
  179. def startTLS(self):
  180. raise Exception("WSS over explicit proxies not implemented")
  181. class WebSocketAdapterFactory(object):
  182. """
  183. Adapter class for asyncio-based WebSocket client and server factories.
  184. """
  185. log = txaio.make_logger()
  186. def __call__(self):
  187. proto = self.protocol()
  188. proto.factory = self
  189. return proto
  190. @public
  191. class WebSocketServerFactory(WebSocketAdapterFactory, protocol.WebSocketServerFactory):
  192. """
  193. Base class for asyncio-based WebSocket server factories.
  194. Implements:
  195. * :class:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory`
  196. """
  197. log = txaio.make_logger()
  198. protocol = WebSocketServerProtocol
  199. def __init__(self, *args, **kwargs):
  200. """
  201. .. note::
  202. In addition to all arguments to the constructor of
  203. :meth:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory`,
  204. you can supply a ``loop`` keyword argument to specify the
  205. asyncio event loop to be used.
  206. """
  207. loop = kwargs.pop('loop', None)
  208. self.loop = loop or asyncio.get_event_loop()
  209. protocol.WebSocketServerFactory.__init__(self, *args, **kwargs)
  210. @public
  211. class WebSocketClientFactory(WebSocketAdapterFactory, protocol.WebSocketClientFactory):
  212. """
  213. Base class for asyncio-based WebSocket client factories.
  214. Implements:
  215. * :class:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory`
  216. """
  217. log = txaio.make_logger()
  218. def __init__(self, *args, **kwargs):
  219. """
  220. .. note::
  221. In addition to all arguments to the constructor of
  222. :meth:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory`,
  223. you can supply a ``loop`` keyword argument to specify the
  224. asyncio event loop to be used.
  225. """
  226. loop = kwargs.pop('loop', None)
  227. self.loop = loop or asyncio.get_event_loop()
  228. protocol.WebSocketClientFactory.__init__(self, *args, **kwargs)
  229. @public
  230. class WampWebSocketServerProtocol(websocket.WampWebSocketServerProtocol, WebSocketServerProtocol):
  231. """
  232. asyncio-based WAMP-over-WebSocket server protocol.
  233. Implements:
  234. * :class:`autobahn.wamp.interfaces.ITransport`
  235. """
  236. log = txaio.make_logger()
  237. @public
  238. class WampWebSocketServerFactory(websocket.WampWebSocketServerFactory, WebSocketServerFactory):
  239. """
  240. asyncio-based WAMP-over-WebSocket server factory.
  241. """
  242. log = txaio.make_logger()
  243. protocol = WampWebSocketServerProtocol
  244. def __init__(self, factory, *args, **kwargs):
  245. """
  246. :param factory: A callable that produces instances that implement
  247. :class:`autobahn.wamp.interfaces.ITransportHandler`
  248. :type factory: callable
  249. :param serializers: A list of WAMP serializers to use (or ``None``
  250. for all available serializers).
  251. :type serializers: list of objects implementing
  252. :class:`autobahn.wamp.interfaces.ISerializer`
  253. """
  254. serializers = kwargs.pop('serializers', None)
  255. websocket.WampWebSocketServerFactory.__init__(self, factory, serializers)
  256. kwargs['protocols'] = self._protocols
  257. # noinspection PyCallByClass
  258. WebSocketServerFactory.__init__(self, *args, **kwargs)
  259. @public
  260. class WampWebSocketClientProtocol(websocket.WampWebSocketClientProtocol, WebSocketClientProtocol):
  261. """
  262. asyncio-based WAMP-over-WebSocket client protocols.
  263. Implements:
  264. * :class:`autobahn.wamp.interfaces.ITransport`
  265. """
  266. log = txaio.make_logger()
  267. @public
  268. class WampWebSocketClientFactory(websocket.WampWebSocketClientFactory, WebSocketClientFactory):
  269. """
  270. asyncio-based WAMP-over-WebSocket client factory.
  271. """
  272. log = txaio.make_logger()
  273. protocol = WampWebSocketClientProtocol
  274. def __init__(self, factory, *args, **kwargs):
  275. """
  276. :param factory: A callable that produces instances that implement
  277. :class:`autobahn.wamp.interfaces.ITransportHandler`
  278. :type factory: callable
  279. :param serializer: The WAMP serializer to use (or ``None`` for
  280. "best" serializer, chosen as the first serializer available from
  281. this list: CBOR, MessagePack, UBJSON, JSON).
  282. :type serializer: object implementing :class:`autobahn.wamp.interfaces.ISerializer`
  283. """
  284. serializers = kwargs.pop('serializers', None)
  285. websocket.WampWebSocketClientFactory.__init__(self, factory, serializers)
  286. kwargs['protocols'] = self._protocols
  287. WebSocketClientFactory.__init__(self, *args, **kwargs)