import datetime import json from typing import Dict, List, Any import requests from geventwebsocket import WebSocketError from geventwebsocket.websocket import WebSocket import model from debug import debug from my_types import MessageType, Message, UserIdentification, UserId, MessageQueue from routes import push_message_types PORT = 58317 if debug: host = 'http://localhost:' + str(PORT) else: host = 'http://koljastrohm-games.com:' + str(PORT) websockets_for_user: Dict[UserIdentification, List[WebSocket]] = {} users_for_websocket: Dict[WebSocket, List[UserIdentification]] = {} push_message_queue: MessageQueue = [] session_id = None class HttpError(Exception): def __init__(self, code: int, message: Any = None, prepend_code=True): Exception.__init__(self) self.body = dict() self.code = code if message is None: message = str(code) if prepend_code and isinstance(message, str) and not message.startswith(str(code)): message = str(code) + ': ' + message if code // 100 == 2: self.body['message'] = message else: self.body['error'] = message elif isinstance(message, str): if code // 100 == 2: self.body['message'] = message else: self.body['error'] = message elif isinstance(message, dict): self.body = message.copy() else: # for example a list or number self.body['data'] = message class NotFound(HttpError): def __init__(self, message: Any = None, prepend_code=True): HttpError.__init__(self, 404, message, prepend_code) class Success(HttpError): def __init__(self, message: Any = None, prepend_code=False): HttpError.__init__(self, 200, message, prepend_code) class UnavailableForLegalReasons(HttpError): def __init__(self, message: Any = None, prepend_code=True): HttpError.__init__(self, 451, message, prepend_code) class Forbidden(HttpError): def __init__(self, message: Any = None, prepend_code=True): HttpError.__init__(self, 403, message, prepend_code) class Unauthorized(HttpError): def __init__(self, message: Any = None, prepend_code=True): HttpError.__init__(self, 401, message, prepend_code) class BadRequest(HttpError): def __init__(self, message: Any = None, prepend_code=True): HttpError.__init__(self, 400, message, prepend_code) class InternalServerError(HttpError): def __init__(self, message: Any = None, prepend_code=True): HttpError.__init__(self, 500, message, prepend_code) class Locked(HttpError): def __init__(self, message: Any = None, prepend_code=True): HttpError.__init__(self, 423, message, prepend_code) class PreconditionFailed(HttpError): def __init__(self, message: Any = None, prepend_code=True): HttpError.__init__(self, 412, message, prepend_code) def check_missing_attributes(request_json: Dict, attributes: List[str]): for attr in attributes: if attr not in request_json: if str(attr) == 'session_id': raise Unauthorized('You are not signed in.') raise BadRequest('Missing value for attribute ' + str(attr)) if str(attr) == 'session_id': if not model.valid_session_id(request_json['session_id']): raise Unauthorized('You are not signed in.') def client_request(route, data=None): if data is None: data = {} return json_request(host + '/json/' + route, data) def json_request(url, data): if debug: print('Sending to ' + url + ': ' + str(json.dumps(data))) r = requests.post(url, data=json.dumps(data), headers={'Content-type': 'application/json; charset=latin-1'}) if debug: print('Request returned: ' + str(r.content)) return r.json() def push_message(recipient_ids: List[UserIdentification], contents: Message, message_type: MessageType): if message_type not in push_message_types: raise AssertionError('Invalid message type.') sockets = {socket for user_id in recipient_ids for socket in websockets_for_user.get(user_id, [])} if len(sockets) > 0: message = {'message_type': message_type, 'contents': contents} for ws in sockets: if ws.closed: ws_cleanup(ws) continue message = json.dumps({'message_type': message_type, 'contents': contents}) ws.send(message) print(message_type, 'to', len(sockets), 'sockets', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), len(message)) def enqueue_push_message(recipient_ids: List[UserId], contents: Dict, message_type: str): assert message_type in push_message_types recipient_ids = [user_id for user_id in recipient_ids] if len(recipient_ids) == 0: return recipient_ids = [(model.current_db_name, user_id) for user_id in recipient_ids] push_message_queue.append((recipient_ids, contents, message_type)) def ws_cleanup(ws): if ws in users_for_websocket: users_affected = users_for_websocket[ws] for user_id in users_affected: # remove the websocket from all lists websockets_for_user[user_id][:] = filter(lambda s: s != ws, websockets_for_user[user_id]) del users_for_websocket[ws] if not ws.closed: ws.close() print('websocket connection ended', *ws.handler.client_address, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ) def preprocess_push_message_queue(queue: MessageQueue) -> MessageQueue: return queue def push_messages_in_queue(): global push_message_queue push_message_queue = preprocess_push_message_queue(push_message_queue) for message in push_message_queue: try: push_message(*message) except WebSocketError: continue except ConnectionResetError: continue del push_message_queue[:]