diff libervia/server/websockets.py @ 1124:28e3eb3bb217

files reorganisation and installation rework: - files have been reorganised to follow other SàT projects and usual Python organisation (no more "/src" directory) - VERSION file is now used, as for other SàT projects - replace the overcomplicated setup.py be a more sane one. Pyjamas part is not compiled anymore by setup.py, it must be done separatly - removed check for data_dir if it's empty - installation tested working in virtual env - libervia launching script is now in bin/libervia
author Goffi <goffi@goffi.org>
date Sat, 25 Aug 2018 17:59:48 +0200
parents src/server/websockets.py@cdd389ef97bc
children 2af117bfe6cc
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libervia/server/websockets.py	Sat Aug 25 17:59:48 2018 +0200
@@ -0,0 +1,148 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Libervia: a Salut à Toi frontend
+# Copyright (C) 2011-2018 Jérôme Poisson <goffi@goffi.org>
+
+# 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 <http://www.gnu.org/licenses/>.
+
+from sat.core.i18n import _
+from sat.core.log import getLogger
+
+log = getLogger(__name__)
+from sat.core import exceptions
+
+from autobahn.twisted import websocket
+from autobahn.twisted import resource as resource
+from autobahn.websocket import types
+
+import json
+
+LIBERVIA_PROTOCOL = "libervia_page"
+
+
+class LiberviaPageWSProtocol(websocket.WebSocketServerProtocol):
+    host = None
+    tokens_map = {}
+
+    def onConnect(self, request):
+        prefix = LIBERVIA_PROTOCOL + u"_"
+        for protocol in request.protocols:
+            if protocol.startswith(prefix):
+                token = protocol[len(prefix) :].strip()
+                if token:
+                    break
+        else:
+            raise types.ConnectionDeny(
+                types.ConnectionDeny.NOT_IMPLEMENTED, u"Can't use this subprotocol"
+            )
+
+        if token not in self.tokens_map:
+            log.warning(_(u"Can't activate page socket: unknown token"))
+            raise types.ConnectionDeny(
+                types.ConnectionDeny.FORBIDDEN, u"Bad token, please reload page"
+            )
+        self.token = token
+        self.page = self.tokens_map[token]["page"]
+        self.request = self.tokens_map[token]["request"]
+        return protocol
+
+    def onOpen(self):
+        log.debug(
+            _(
+                u"Websocket opened for {page} (token: {token})".format(
+                    page=self.page, token=self.token
+                )
+            )
+        )
+        self.request.sendData = self.sendJSONData
+        self.page.onSocketOpen(self.request)
+
+    def onMessage(self, payload, isBinary):
+        try:
+            data_json = json.loads(payload.decode("utf8"))
+        except ValueError as e:
+            log.warning(
+                _(u"Not valid JSON, ignoring data: {msg}\n{data}").format(
+                    msg=e, data=payload
+                )
+            )
+            return
+        #  we request page first, to raise an AttributeError
+        # if it is not set (which should never happen)
+        page = self.page
+        try:
+            cb = page.on_data
+        except AttributeError:
+            log.warning(
+                _(
+                    u'No "on_data" method set on dynamic page, ignoring data:\n{data}'
+                ).format(data=data_json)
+            )
+        else:
+            cb(page, self.request, data_json)
+
+    def onClose(self, wasClean, code, reason):
+        try:
+            token = self.token
+        except AttributeError:
+            log.warning(_(u"Websocket closed but no token is associated"))
+            return
+
+        self.page.onSocketClose(self.request)
+
+        try:
+            del self.tokens_map[token]
+            del self.request.sendData
+        except (KeyError, AttributeError):
+            raise exceptions.InternalError(
+                _(u"Token or sendData doesn't exist, this should never happen!")
+            )
+        log.debug(
+            _(
+                u"Websocket closed for {page} (token: {token}). {reason}".format(
+                    page=self.page,
+                    token=self.token,
+                    reason=u""
+                    if wasClean
+                    else _(u"Reason: {reason}").format(reason=reason),
+                )
+            )
+        )
+
+    def sendJSONData(self, type_, **data):
+        assert "type" not in data
+        data["type"] = type_
+        self.sendMessage(json.dumps(data, ensure_ascii=False).encode("utf8"))
+
+    @classmethod
+    def getBaseURL(cls, host, secure):
+        return u"ws{sec}://localhost:{port}".format(
+            sec="s" if secure else "",
+            port=cls.host.options["port_https" if secure else "port"],
+        )
+
+    @classmethod
+    def getResource(cls, host, secure):
+        if cls.host is None:
+            cls.host = host
+        factory = websocket.WebSocketServerFactory(cls.getBaseURL(host, secure))
+        factory.protocol = cls
+        return resource.WebSocketResource(factory)
+
+    @classmethod
+    def registerToken(cls, token, page, request):
+        if token in cls.tokens_map:
+            raise exceptions.ConflictError(_(u"This token is already registered"))
+        cls.tokens_map[token] = {"page": page, "request": request}