aboutsummaryrefslogtreecommitdiff
path: root/diplomacy/server/connection_handler.py
diff options
context:
space:
mode:
Diffstat (limited to 'diplomacy/server/connection_handler.py')
-rw-r--r--diplomacy/server/connection_handler.py111
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.')