diff options
Diffstat (limited to 'diplomacy/server/connection_handler.py')
-rw-r--r-- | diplomacy/server/connection_handler.py | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/diplomacy/server/connection_handler.py b/diplomacy/server/connection_handler.py new file mode 100644 index 0000000..0089db3 --- /dev/null +++ b/diplomacy/server/connection_handler.py @@ -0,0 +1,111 @@ +# ============================================================================== +# Copyright (C) 2019 - Philip Paquette, Steven Bocco +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along +# with this program. If not, see <https://www.gnu.org/licenses/>. +# ============================================================================== +""" Tornado connection handler class, used internally to manage data received by server application. """ +import logging + +from urllib.parse import urlparse +from tornado import gen +from tornado.websocket import WebSocketHandler, WebSocketClosedError + +import ujson as json + +from diplomacy.communication import responses, requests +from diplomacy.server import request_managers +from diplomacy.utils import exceptions, strings + + +LOGGER = logging.getLogger(__name__) + +class ConnectionHandler(WebSocketHandler): + """ ConnectionHandler class. Properties: + - server: server object representing running server. + """ + # pylint: disable=abstract-method + + def __init__(self, *args, **kwargs): + self.server = None + super(ConnectionHandler, self).__init__(*args, **kwargs) + + def initialize(self, server=None): + """ Initialize the connection handler. + :param server: a Server object. + :type server: diplomacy.Server + """ + # pylint: disable=arguments-differ + if self.server is None: + self.server = server + + def get_compression_options(self): + """ Return compression options for the connection (see parent method). + Non-None enables compression with default options. + """ + return {} + + def check_origin(self, origin): + """ Return True if we should accept connexion from given origin (str). """ + + # It seems origin may be 'null', e.g. if client is a web page loaded from disk (`file:///my_test_file.html`). + # Accept it. + if origin == 'null': + return True + + # Try to check if origin matches host (without regarding port). + # Adapted from parent method code (tornado 4.5.3). + parsed_origin = urlparse(origin) + origin = parsed_origin.netloc.split(':')[0] + origin = origin.lower() + # Split host with ':' and keep only first piece to ignore eventual port. + host = self.request.headers.get("Host").split(':')[0] + return origin == host + + def on_close(self): + """ Invoked when the socket is closed (see parent method). + Detach this connection handler from server users. + """ + self.server.users.remove_connection(self, remove_tokens=False) + LOGGER.info("Removed connection. Remaining %d connection(s).", self.server.users.count_connections()) + + @gen.coroutine + def on_message(self, message): + """ Parse given message and manage parsed data (expected a string representation of a request). """ + try: + json_request = json.loads(message) + if not isinstance(json_request, dict): + raise ValueError("Unable to convert a JSON string to a dictionary.") + except ValueError as exc: + # Error occurred because either message is not a JSON string or parsed JSON object is not a dict. + response = responses.Error(message='%s/%s' % (type(exc).__name__, str(exc))) + else: + try: + request = requests.parse_dict(json_request) + + if request.level is not None: + # Link request token to this connection handler. + self.server.users.attach_connection_handler(request.token, self) + + response = yield request_managers.handle_request(self.server, request, self) + if response is None: + response = responses.Ok(request_id=request.request_id) + + except exceptions.ResponseException as exc: + response = responses.Error(message='%s/%s' % (type(exc).__name__, exc.message), + request_id=json_request.get(strings.REQUEST_ID, None)) + + try: + yield self.write_message(response.json()) + except WebSocketClosedError: + LOGGER.error('Websocket is closed.') |