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.

wamp.py 11KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  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. import signal
  28. import txaio
  29. txaio.use_asyncio() # noqa
  30. from autobahn.util import public
  31. from autobahn.wamp import protocol
  32. from autobahn.wamp.types import ComponentConfig
  33. from autobahn.websocket.util import parse_url as parse_ws_url
  34. from autobahn.rawsocket.util import parse_url as parse_rs_url
  35. from autobahn.asyncio.websocket import WampWebSocketClientFactory
  36. from autobahn.asyncio.rawsocket import WampRawSocketClientFactory
  37. from autobahn.websocket.compress import PerMessageDeflateOffer, \
  38. PerMessageDeflateResponse, PerMessageDeflateResponseAccept
  39. from autobahn.wamp.interfaces import ITransportHandler, ISession
  40. __all__ = (
  41. 'ApplicationSession',
  42. 'ApplicationSessionFactory',
  43. 'ApplicationRunner'
  44. )
  45. @public
  46. class ApplicationSession(protocol.ApplicationSession):
  47. """
  48. WAMP application session for asyncio-based applications.
  49. Implements:
  50. * ``autobahn.wamp.interfaces.ITransportHandler``
  51. * ``autobahn.wamp.interfaces.ISession``
  52. """
  53. log = txaio.make_logger()
  54. ITransportHandler.register(ApplicationSession)
  55. # ISession.register collides with the abc.ABCMeta.register method
  56. ISession.abc_register(ApplicationSession)
  57. class ApplicationSessionFactory(protocol.ApplicationSessionFactory):
  58. """
  59. WAMP application session factory for asyncio-based applications.
  60. """
  61. session: ApplicationSession = ApplicationSession
  62. """
  63. The application session class this application session factory will use.
  64. Defaults to :class:`autobahn.asyncio.wamp.ApplicationSession`.
  65. """
  66. log = txaio.make_logger()
  67. @public
  68. class ApplicationRunner(object):
  69. """
  70. This class is a convenience tool mainly for development and quick hosting
  71. of WAMP application components.
  72. It can host a WAMP application component in a WAMP-over-WebSocket client
  73. connecting to a WAMP router.
  74. """
  75. log = txaio.make_logger()
  76. def __init__(self,
  77. url,
  78. realm=None,
  79. extra=None,
  80. serializers=None,
  81. ssl=None,
  82. proxy=None,
  83. headers=None):
  84. """
  85. :param url: The WebSocket URL of the WAMP router to connect to (e.g. `ws://somehost.com:8090/somepath`)
  86. :type url: str
  87. :param realm: The WAMP realm to join the application session to.
  88. :type realm: str
  89. :param extra: Optional extra configuration to forward to the application component.
  90. :type extra: dict
  91. :param serializers: A list of WAMP serializers to use (or None for default serializers).
  92. Serializers must implement :class:`autobahn.wamp.interfaces.ISerializer`.
  93. :type serializers: list
  94. :param ssl: An (optional) SSL context instance or a bool. See
  95. the documentation for the `loop.create_connection` asyncio
  96. method, to which this value is passed as the ``ssl``
  97. keyword parameter.
  98. :type ssl: :class:`ssl.SSLContext` or bool
  99. :param proxy: Explicit proxy server to use; a dict with ``host`` and ``port`` keys
  100. :type proxy: dict or None
  101. :param headers: Additional headers to send (only applies to WAMP-over-WebSocket).
  102. :type headers: dict
  103. """
  104. assert(type(url) == str)
  105. assert(realm is None or type(realm) == str)
  106. assert(extra is None or type(extra) == dict)
  107. assert(headers is None or type(headers) == dict)
  108. assert(proxy is None or type(proxy) == dict)
  109. self.url = url
  110. self.realm = realm
  111. self.extra = extra or dict()
  112. self.serializers = serializers
  113. self.ssl = ssl
  114. self.proxy = proxy
  115. self.headers = headers
  116. @public
  117. def stop(self):
  118. """
  119. Stop reconnecting, if auto-reconnecting was enabled.
  120. """
  121. raise NotImplementedError()
  122. @public
  123. def run(self, make, start_loop=True, log_level='info'):
  124. """
  125. Run the application component. Under the hood, this runs the event
  126. loop (unless `start_loop=False` is passed) so won't return
  127. until the program is done.
  128. :param make: A factory that produces instances of :class:`autobahn.asyncio.wamp.ApplicationSession`
  129. when called with an instance of :class:`autobahn.wamp.types.ComponentConfig`.
  130. :type make: callable
  131. :param start_loop: When ``True`` (the default) this method
  132. start a new asyncio loop.
  133. :type start_loop: bool
  134. :returns: None is returned, unless you specify
  135. `start_loop=False` in which case the coroutine from calling
  136. `loop.create_connection()` is returned. This will yield the
  137. (transport, protocol) pair.
  138. """
  139. if callable(make):
  140. def create():
  141. cfg = ComponentConfig(self.realm, self.extra)
  142. try:
  143. session = make(cfg)
  144. except Exception as e:
  145. self.log.error('ApplicationSession could not be instantiated: {}'.format(e))
  146. loop = asyncio.get_event_loop()
  147. if loop.is_running():
  148. loop.stop()
  149. raise
  150. else:
  151. return session
  152. else:
  153. create = make
  154. if self.url.startswith('rs'):
  155. # try to parse RawSocket URL ..
  156. isSecure, host, port = parse_rs_url(self.url)
  157. # use the first configured serializer if any (which means, auto-choose "best")
  158. serializer = self.serializers[0] if self.serializers else None
  159. # create a WAMP-over-RawSocket transport client factory
  160. transport_factory = WampRawSocketClientFactory(create, serializer=serializer)
  161. else:
  162. # try to parse WebSocket URL ..
  163. isSecure, host, port, resource, path, params = parse_ws_url(self.url)
  164. # create a WAMP-over-WebSocket transport client factory
  165. transport_factory = WampWebSocketClientFactory(create, url=self.url, serializers=self.serializers, proxy=self.proxy, headers=self.headers)
  166. # client WebSocket settings - similar to:
  167. # - http://crossbar.io/docs/WebSocket-Compression/#production-settings
  168. # - http://crossbar.io/docs/WebSocket-Options/#production-settings
  169. # The permessage-deflate extensions offered to the server ..
  170. offers = [PerMessageDeflateOffer()]
  171. # Function to accept permessage_delate responses from the server ..
  172. def accept(response):
  173. if isinstance(response, PerMessageDeflateResponse):
  174. return PerMessageDeflateResponseAccept(response)
  175. # set WebSocket options for all client connections
  176. transport_factory.setProtocolOptions(maxFramePayloadSize=1048576,
  177. maxMessagePayloadSize=1048576,
  178. autoFragmentSize=65536,
  179. failByDrop=False,
  180. openHandshakeTimeout=2.5,
  181. closeHandshakeTimeout=1.,
  182. tcpNoDelay=True,
  183. autoPingInterval=10.,
  184. autoPingTimeout=5.,
  185. autoPingSize=12,
  186. perMessageCompressionOffers=offers,
  187. perMessageCompressionAccept=accept)
  188. # SSL context for client connection
  189. if self.ssl is None:
  190. ssl = isSecure
  191. else:
  192. if self.ssl and not isSecure:
  193. raise RuntimeError(
  194. 'ssl argument value passed to %s conflicts with the "ws:" '
  195. 'prefix of the url argument. Did you mean to use "wss:"?' %
  196. self.__class__.__name__)
  197. ssl = self.ssl
  198. # start the client connection
  199. loop = asyncio.get_event_loop()
  200. if loop.is_closed() and start_loop:
  201. asyncio.set_event_loop(asyncio.new_event_loop())
  202. loop = asyncio.get_event_loop()
  203. if hasattr(transport_factory, 'loop'):
  204. transport_factory.loop = loop
  205. # assure we are using asyncio
  206. # txaio.use_asyncio()
  207. assert txaio._explicit_framework == 'asyncio'
  208. txaio.config.loop = loop
  209. coro = loop.create_connection(transport_factory, host, port, ssl=ssl)
  210. # start a asyncio loop
  211. if not start_loop:
  212. return coro
  213. else:
  214. (transport, protocol) = loop.run_until_complete(coro)
  215. # start logging
  216. txaio.start_logging(level=log_level)
  217. try:
  218. loop.add_signal_handler(signal.SIGTERM, loop.stop)
  219. except NotImplementedError:
  220. # signals are not available on Windows
  221. pass
  222. # 4) now enter the asyncio event loop
  223. try:
  224. loop.run_forever()
  225. except KeyboardInterrupt:
  226. # wait until we send Goodbye if user hit ctrl-c
  227. # (done outside this except so SIGTERM gets the same handling)
  228. pass
  229. # give Goodbye message a chance to go through, if we still
  230. # have an active session
  231. if protocol._session:
  232. loop.run_until_complete(protocol._session.leave())
  233. loop.close()
  234. # new API
  235. class Session(protocol._SessionShim):
  236. # XXX these methods are redundant, but put here for possibly
  237. # better clarity; maybe a bad idea.
  238. def on_welcome(self, welcome_msg):
  239. pass
  240. def on_join(self, details):
  241. pass
  242. def on_leave(self, details):
  243. self.disconnect()
  244. def on_connect(self):
  245. self.join(self.config.realm)
  246. def on_disconnect(self):
  247. pass