comparison 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
comparison
equal deleted inserted replaced
1123:63a4b8fe9782 1124:28e3eb3bb217
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # Libervia: a Salut à Toi frontend
5 # Copyright (C) 2011-2018 Jérôme Poisson <goffi@goffi.org>
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 from sat.core.i18n import _
21 from sat.core.log import getLogger
22
23 log = getLogger(__name__)
24 from sat.core import exceptions
25
26 from autobahn.twisted import websocket
27 from autobahn.twisted import resource as resource
28 from autobahn.websocket import types
29
30 import json
31
32 LIBERVIA_PROTOCOL = "libervia_page"
33
34
35 class LiberviaPageWSProtocol(websocket.WebSocketServerProtocol):
36 host = None
37 tokens_map = {}
38
39 def onConnect(self, request):
40 prefix = LIBERVIA_PROTOCOL + u"_"
41 for protocol in request.protocols:
42 if protocol.startswith(prefix):
43 token = protocol[len(prefix) :].strip()
44 if token:
45 break
46 else:
47 raise types.ConnectionDeny(
48 types.ConnectionDeny.NOT_IMPLEMENTED, u"Can't use this subprotocol"
49 )
50
51 if token not in self.tokens_map:
52 log.warning(_(u"Can't activate page socket: unknown token"))
53 raise types.ConnectionDeny(
54 types.ConnectionDeny.FORBIDDEN, u"Bad token, please reload page"
55 )
56 self.token = token
57 self.page = self.tokens_map[token]["page"]
58 self.request = self.tokens_map[token]["request"]
59 return protocol
60
61 def onOpen(self):
62 log.debug(
63 _(
64 u"Websocket opened for {page} (token: {token})".format(
65 page=self.page, token=self.token
66 )
67 )
68 )
69 self.request.sendData = self.sendJSONData
70 self.page.onSocketOpen(self.request)
71
72 def onMessage(self, payload, isBinary):
73 try:
74 data_json = json.loads(payload.decode("utf8"))
75 except ValueError as e:
76 log.warning(
77 _(u"Not valid JSON, ignoring data: {msg}\n{data}").format(
78 msg=e, data=payload
79 )
80 )
81 return
82 #  we request page first, to raise an AttributeError
83 # if it is not set (which should never happen)
84 page = self.page
85 try:
86 cb = page.on_data
87 except AttributeError:
88 log.warning(
89 _(
90 u'No "on_data" method set on dynamic page, ignoring data:\n{data}'
91 ).format(data=data_json)
92 )
93 else:
94 cb(page, self.request, data_json)
95
96 def onClose(self, wasClean, code, reason):
97 try:
98 token = self.token
99 except AttributeError:
100 log.warning(_(u"Websocket closed but no token is associated"))
101 return
102
103 self.page.onSocketClose(self.request)
104
105 try:
106 del self.tokens_map[token]
107 del self.request.sendData
108 except (KeyError, AttributeError):
109 raise exceptions.InternalError(
110 _(u"Token or sendData doesn't exist, this should never happen!")
111 )
112 log.debug(
113 _(
114 u"Websocket closed for {page} (token: {token}). {reason}".format(
115 page=self.page,
116 token=self.token,
117 reason=u""
118 if wasClean
119 else _(u"Reason: {reason}").format(reason=reason),
120 )
121 )
122 )
123
124 def sendJSONData(self, type_, **data):
125 assert "type" not in data
126 data["type"] = type_
127 self.sendMessage(json.dumps(data, ensure_ascii=False).encode("utf8"))
128
129 @classmethod
130 def getBaseURL(cls, host, secure):
131 return u"ws{sec}://localhost:{port}".format(
132 sec="s" if secure else "",
133 port=cls.host.options["port_https" if secure else "port"],
134 )
135
136 @classmethod
137 def getResource(cls, host, secure):
138 if cls.host is None:
139 cls.host = host
140 factory = websocket.WebSocketServerFactory(cls.getBaseURL(host, secure))
141 factory.protocol = cls
142 return resource.WebSocketResource(factory)
143
144 @classmethod
145 def registerToken(cls, token, page, request):
146 if token in cls.tokens_map:
147 raise exceptions.ConflictError(_(u"This token is already registered"))
148 cls.tokens_map[token] = {"page": page, "request": request}