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 8.4KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import json
  2. from asgiref.sync import async_to_sync
  3. from ..consumer import AsyncConsumer, SyncConsumer
  4. from ..exceptions import (
  5. AcceptConnection,
  6. DenyConnection,
  7. InvalidChannelLayerError,
  8. StopConsumer,
  9. )
  10. class WebsocketConsumer(SyncConsumer):
  11. """
  12. Base WebSocket consumer. Provides a general encapsulation for the
  13. WebSocket handling model that other applications can build on.
  14. """
  15. groups = None
  16. def __init__(self, *args, **kwargs):
  17. if self.groups is None:
  18. self.groups = []
  19. def websocket_connect(self, message):
  20. """
  21. Called when a WebSocket connection is opened.
  22. """
  23. try:
  24. for group in self.groups:
  25. async_to_sync(self.channel_layer.group_add)(group, self.channel_name)
  26. except AttributeError:
  27. raise InvalidChannelLayerError(
  28. "BACKEND is unconfigured or doesn't support groups"
  29. )
  30. try:
  31. self.connect()
  32. except AcceptConnection:
  33. self.accept()
  34. except DenyConnection:
  35. self.close()
  36. def connect(self):
  37. self.accept()
  38. def accept(self, subprotocol=None):
  39. """
  40. Accepts an incoming socket
  41. """
  42. super().send({"type": "websocket.accept", "subprotocol": subprotocol})
  43. def websocket_receive(self, message):
  44. """
  45. Called when a WebSocket frame is received. Decodes it and passes it
  46. to receive().
  47. """
  48. if "text" in message:
  49. self.receive(text_data=message["text"])
  50. else:
  51. self.receive(bytes_data=message["bytes"])
  52. def receive(self, text_data=None, bytes_data=None):
  53. """
  54. Called with a decoded WebSocket frame.
  55. """
  56. pass
  57. def send(self, text_data=None, bytes_data=None, close=False):
  58. """
  59. Sends a reply back down the WebSocket
  60. """
  61. if text_data is not None:
  62. super().send({"type": "websocket.send", "text": text_data})
  63. elif bytes_data is not None:
  64. super().send({"type": "websocket.send", "bytes": bytes_data})
  65. else:
  66. raise ValueError("You must pass one of bytes_data or text_data")
  67. if close:
  68. self.close(close)
  69. def close(self, code=None):
  70. """
  71. Closes the WebSocket from the server end
  72. """
  73. if code is not None and code is not True:
  74. super().send({"type": "websocket.close", "code": code})
  75. else:
  76. super().send({"type": "websocket.close"})
  77. def websocket_disconnect(self, message):
  78. """
  79. Called when a WebSocket connection is closed. Base level so you don't
  80. need to call super() all the time.
  81. """
  82. try:
  83. for group in self.groups:
  84. async_to_sync(self.channel_layer.group_discard)(
  85. group, self.channel_name
  86. )
  87. except AttributeError:
  88. raise InvalidChannelLayerError(
  89. "BACKEND is unconfigured or doesn't support groups"
  90. )
  91. self.disconnect(message["code"])
  92. raise StopConsumer()
  93. def disconnect(self, code):
  94. """
  95. Called when a WebSocket connection is closed.
  96. """
  97. pass
  98. class JsonWebsocketConsumer(WebsocketConsumer):
  99. """
  100. Variant of WebsocketConsumer that automatically JSON-encodes and decodes
  101. messages as they come in and go out. Expects everything to be text; will
  102. error on binary data.
  103. """
  104. def receive(self, text_data=None, bytes_data=None, **kwargs):
  105. if text_data:
  106. self.receive_json(self.decode_json(text_data), **kwargs)
  107. else:
  108. raise ValueError("No text section for incoming WebSocket frame!")
  109. def receive_json(self, content, **kwargs):
  110. """
  111. Called with decoded JSON content.
  112. """
  113. pass
  114. def send_json(self, content, close=False):
  115. """
  116. Encode the given content as JSON and send it to the client.
  117. """
  118. super().send(text_data=self.encode_json(content), close=close)
  119. @classmethod
  120. def decode_json(cls, text_data):
  121. return json.loads(text_data)
  122. @classmethod
  123. def encode_json(cls, content):
  124. return json.dumps(content)
  125. class AsyncWebsocketConsumer(AsyncConsumer):
  126. """
  127. Base WebSocket consumer, async version. Provides a general encapsulation
  128. for the WebSocket handling model that other applications can build on.
  129. """
  130. groups = None
  131. def __init__(self, *args, **kwargs):
  132. if self.groups is None:
  133. self.groups = []
  134. async def websocket_connect(self, message):
  135. """
  136. Called when a WebSocket connection is opened.
  137. """
  138. try:
  139. for group in self.groups:
  140. await self.channel_layer.group_add(group, self.channel_name)
  141. except AttributeError:
  142. raise InvalidChannelLayerError(
  143. "BACKEND is unconfigured or doesn't support groups"
  144. )
  145. try:
  146. await self.connect()
  147. except AcceptConnection:
  148. await self.accept()
  149. except DenyConnection:
  150. await self.close()
  151. async def connect(self):
  152. await self.accept()
  153. async def accept(self, subprotocol=None):
  154. """
  155. Accepts an incoming socket
  156. """
  157. await super().send({"type": "websocket.accept", "subprotocol": subprotocol})
  158. async def websocket_receive(self, message):
  159. """
  160. Called when a WebSocket frame is received. Decodes it and passes it
  161. to receive().
  162. """
  163. if "text" in message:
  164. await self.receive(text_data=message["text"])
  165. else:
  166. await self.receive(bytes_data=message["bytes"])
  167. async def receive(self, text_data=None, bytes_data=None):
  168. """
  169. Called with a decoded WebSocket frame.
  170. """
  171. pass
  172. async def send(self, text_data=None, bytes_data=None, close=False):
  173. """
  174. Sends a reply back down the WebSocket
  175. """
  176. if text_data is not None:
  177. await super().send({"type": "websocket.send", "text": text_data})
  178. elif bytes_data is not None:
  179. await super().send({"type": "websocket.send", "bytes": bytes_data})
  180. else:
  181. raise ValueError("You must pass one of bytes_data or text_data")
  182. if close:
  183. await self.close(close)
  184. async def close(self, code=None):
  185. """
  186. Closes the WebSocket from the server end
  187. """
  188. if code is not None and code is not True:
  189. await super().send({"type": "websocket.close", "code": code})
  190. else:
  191. await super().send({"type": "websocket.close"})
  192. async def websocket_disconnect(self, message):
  193. """
  194. Called when a WebSocket connection is closed. Base level so you don't
  195. need to call super() all the time.
  196. """
  197. try:
  198. for group in self.groups:
  199. await self.channel_layer.group_discard(group, self.channel_name)
  200. except AttributeError:
  201. raise InvalidChannelLayerError(
  202. "BACKEND is unconfigured or doesn't support groups"
  203. )
  204. await self.disconnect(message["code"])
  205. raise StopConsumer()
  206. async def disconnect(self, code):
  207. """
  208. Called when a WebSocket connection is closed.
  209. """
  210. pass
  211. class AsyncJsonWebsocketConsumer(AsyncWebsocketConsumer):
  212. """
  213. Variant of AsyncWebsocketConsumer that automatically JSON-encodes and decodes
  214. messages as they come in and go out. Expects everything to be text; will
  215. error on binary data.
  216. """
  217. async def receive(self, text_data=None, bytes_data=None, **kwargs):
  218. if text_data:
  219. await self.receive_json(await self.decode_json(text_data), **kwargs)
  220. else:
  221. raise ValueError("No text section for incoming WebSocket frame!")
  222. async def receive_json(self, content, **kwargs):
  223. """
  224. Called with decoded JSON content.
  225. """
  226. pass
  227. async def send_json(self, content, close=False):
  228. """
  229. Encode the given content as JSON and send it to the client.
  230. """
  231. await super().send(text_data=await self.encode_json(content), close=close)
  232. @classmethod
  233. async def decode_json(cls, text_data):
  234. return json.loads(text_data)
  235. @classmethod
  236. async def encode_json(cls, content):
  237. return json.dumps(content)