connection.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import datetime
  2. import json
  3. from typing import Dict, List, Any
  4. import requests
  5. from geventwebsocket import WebSocketError
  6. from geventwebsocket.websocket import WebSocket
  7. import model
  8. from debug import debug
  9. from my_types import MessageType, Message, UserIdentification, UserId, MessageQueue
  10. from routes import push_message_types
  11. PORT = 58317
  12. if debug:
  13. host = 'http://localhost:' + str(PORT)
  14. else:
  15. host = 'http://koljastrohm-games.com:' + str(PORT)
  16. websockets_for_user: Dict[UserIdentification, List[WebSocket]] = {}
  17. users_for_websocket: Dict[WebSocket, List[UserIdentification]] = {}
  18. push_message_queue: MessageQueue = []
  19. session_id = None
  20. class HttpError(Exception):
  21. def __init__(self, code: int, message: Any = None, prepend_code=True):
  22. Exception.__init__(self)
  23. self.body = dict()
  24. self.code = code
  25. if message is None:
  26. message = str(code)
  27. if prepend_code and isinstance(message, str) and not message.startswith(str(code)):
  28. message = str(code) + ': ' + message
  29. if code // 100 == 2:
  30. self.body['message'] = message
  31. else:
  32. self.body['error'] = message
  33. elif isinstance(message, str):
  34. if code // 100 == 2:
  35. self.body['message'] = message
  36. else:
  37. self.body['error'] = message
  38. elif isinstance(message, dict):
  39. self.body = message.copy()
  40. else: # for example a list or number
  41. self.body['data'] = message
  42. class NotFound(HttpError):
  43. def __init__(self, message: Any = None, prepend_code=True):
  44. HttpError.__init__(self, 404, message, prepend_code)
  45. class Success(HttpError):
  46. def __init__(self, message: Any = None, prepend_code=False):
  47. HttpError.__init__(self, 200, message, prepend_code)
  48. class UnavailableForLegalReasons(HttpError):
  49. def __init__(self, message: Any = None, prepend_code=True):
  50. HttpError.__init__(self, 451, message, prepend_code)
  51. class Forbidden(HttpError):
  52. def __init__(self, message: Any = None, prepend_code=True):
  53. HttpError.__init__(self, 403, message, prepend_code)
  54. class Unauthorized(HttpError):
  55. def __init__(self, message: Any = None, prepend_code=True):
  56. HttpError.__init__(self, 401, message, prepend_code)
  57. class BadRequest(HttpError):
  58. def __init__(self, message: Any = None, prepend_code=True):
  59. HttpError.__init__(self, 400, message, prepend_code)
  60. class InternalServerError(HttpError):
  61. def __init__(self, message: Any = None, prepend_code=True):
  62. HttpError.__init__(self, 500, message, prepend_code)
  63. class Locked(HttpError):
  64. def __init__(self, message: Any = None, prepend_code=True):
  65. HttpError.__init__(self, 423, message, prepend_code)
  66. class PreconditionFailed(HttpError):
  67. def __init__(self, message: Any = None, prepend_code=True):
  68. HttpError.__init__(self, 412, message, prepend_code)
  69. def check_missing_attributes(request_json: Dict, attributes: List[str]):
  70. for attr in attributes:
  71. if attr not in request_json:
  72. if str(attr) == 'session_id':
  73. raise Unauthorized('You are not signed in.')
  74. raise BadRequest('Missing value for attribute ' + str(attr))
  75. if str(attr) == 'session_id':
  76. if not model.valid_session_id(request_json['session_id']):
  77. raise Unauthorized('You are not signed in.')
  78. def client_request(route, data=None):
  79. if data is None:
  80. data = {}
  81. return json_request(host + '/json/' + route, data)
  82. def json_request(url, data):
  83. if debug:
  84. print('Sending to ' + url + ': ' + str(json.dumps(data)))
  85. r = requests.post(url,
  86. data=json.dumps(data),
  87. headers={'Content-type': 'application/json; charset=latin-1'})
  88. if debug:
  89. print('Request returned: ' + str(r.content))
  90. return r.json()
  91. def push_message(recipient_ids: List[UserIdentification], contents: Message, message_type: MessageType):
  92. if message_type not in push_message_types:
  93. raise AssertionError('Invalid message type.')
  94. sockets = {socket for user_id in recipient_ids for socket in websockets_for_user.get(user_id, [])}
  95. if len(sockets) > 0:
  96. message = {'message_type': message_type, 'contents': contents}
  97. for ws in sockets:
  98. if ws.closed:
  99. ws_cleanup(ws)
  100. continue
  101. message = json.dumps({'message_type': message_type, 'contents': contents})
  102. ws.send(message)
  103. print(message_type,
  104. 'to',
  105. len(sockets),
  106. 'sockets',
  107. datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  108. len(message))
  109. def enqueue_push_message(recipient_ids: List[UserId], contents: Dict, message_type: str):
  110. assert message_type in push_message_types
  111. recipient_ids = [user_id for user_id in recipient_ids]
  112. if len(recipient_ids) == 0:
  113. return
  114. recipient_ids = [(model.current_db_name, user_id) for user_id in recipient_ids]
  115. push_message_queue.append((recipient_ids, contents, message_type))
  116. def ws_cleanup(ws):
  117. if ws in users_for_websocket:
  118. users_affected = users_for_websocket[ws]
  119. for user_id in users_affected:
  120. # remove the websocket from all lists
  121. websockets_for_user[user_id][:] = filter(lambda s: s != ws,
  122. websockets_for_user[user_id])
  123. del users_for_websocket[ws]
  124. if not ws.closed:
  125. ws.close()
  126. print('websocket connection ended',
  127. *ws.handler.client_address,
  128. datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), )
  129. def preprocess_push_message_queue(queue: MessageQueue) -> MessageQueue:
  130. return queue
  131. def push_messages_in_queue():
  132. global push_message_queue
  133. push_message_queue = preprocess_push_message_queue(push_message_queue)
  134. for message in push_message_queue:
  135. try:
  136. push_message(*message)
  137. except WebSocketError:
  138. continue
  139. except ConnectionResetError:
  140. continue
  141. del push_message_queue[:]