Mercurial > libervia-web
annotate libervia/server/websockets.py @ 1264:54d8b7357267
tasks: new sass task, will compile any Sass file found in root `_browser`
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 04 May 2020 10:18:58 +0200 |
parents | f511f8fbbf8a |
children | 822bd0139769 |
rev | line source |
---|---|
1239 | 1 #!/usr/bin/env python3 |
2 | |
995 | 3 |
4 # Libervia: a Salut à Toi frontend | |
1237 | 5 # Copyright (C) 2011-2020 Jérôme Poisson <goffi@goffi.org> |
995 | 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 | |
1203
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
20 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
21 import json |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
22 from twisted.internet import error |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
23 from autobahn.twisted import websocket |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
24 from autobahn.twisted import resource as resource |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
25 from autobahn.websocket import types |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
26 from sat.core import exceptions |
995 | 27 from sat.core.i18n import _ |
28 from sat.core.log import getLogger | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
29 |
995 | 30 log = getLogger(__name__) |
31 | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
32 LIBERVIA_PROTOCOL = "libervia_page" |
995 | 33 |
34 | |
1203
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
35 class WebsocketRequest(object): |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
36 """Wrapper around autobahn's ConnectionRequest and Twisted's server.Request |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
37 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
38 This is used to have a common interface in Libervia page with request object |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
39 """ |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
40 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
41 def __init__(self, ws_protocol, connection_request, server_request): |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
42 """ |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
43 @param connection_request: websocket request |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
44 @param serveur_request: original request of the page |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
45 """ |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
46 self.ws_protocol = ws_protocol |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
47 self.ws_request = connection_request |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
48 if self.isSecure(): |
1216 | 49 cookie_name = "TWISTED_SECURE_SESSION" |
1203
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
50 else: |
1216 | 51 cookie_name = "TWISTED_SESSION" |
52 cookie_value = server_request.getCookie(cookie_name.encode('utf-8')) | |
1203
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
53 try: |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
54 raw_cookies = ws_protocol.http_headers['cookie'] |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
55 except KeyError: |
1216 | 56 raise ValueError("missing expected cookie header") |
1203
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
57 self.cookies = {k:v for k,v in (c.split('=') for c in raw_cookies.split(';'))} |
1216 | 58 if self.cookies[cookie_name] != cookie_value.decode('utf-8'): |
1203
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
59 raise exceptions.PermissionError( |
1216 | 60 "Bad cookie value, this should never happen.\n" |
61 "headers: {headers}".format(headers=ws_protocol.http_headers)) | |
1203
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
62 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
63 self.template_data = server_request.template_data |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
64 self.data = server_request.data |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
65 self.session = server_request.getSession() |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
66 self._signals_registered = server_request._signals_registered |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
67 self._signals_cache = server_request._signals_cache |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
68 # signal id is needed to link original request with signal handler |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
69 self.signal_id = server_request._signal_id |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
70 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
71 def isSecure(self): |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
72 return self.ws_protocol.factory.isSecure |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
73 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
74 def getSession(self, sessionInterface=None): |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
75 try: |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
76 self.session.touch() |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
77 except (error.AlreadyCalled, error.AlreadyCancelled): |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
78 # Session has already expired. |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
79 self.session = None |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
80 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
81 if sessionInterface: |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
82 return self.session.getComponent(sessionInterface) |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
83 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
84 return self.session |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
85 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
86 def sendData(self, type_, **data): |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
87 assert "type" not in data |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
88 data["type"] = type_ |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
89 self.ws_protocol.sendMessage(json.dumps(data, ensure_ascii=False).encode("utf8")) |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
90 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
91 |
995 | 92 class LiberviaPageWSProtocol(websocket.WebSocketServerProtocol): |
93 host = None | |
94 tokens_map = {} | |
95 | |
96 def onConnect(self, request): | |
1216 | 97 prefix = LIBERVIA_PROTOCOL + "_" |
995 | 98 for protocol in request.protocols: |
99 if protocol.startswith(prefix): | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
100 token = protocol[len(prefix) :].strip() |
995 | 101 if token: |
102 break | |
103 else: | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
104 raise types.ConnectionDeny( |
1216 | 105 types.ConnectionDeny.NOT_IMPLEMENTED, "Can't use this subprotocol" |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
106 ) |
995 | 107 |
108 if token not in self.tokens_map: | |
1216 | 109 log.warning(_("Can't activate page socket: unknown token")) |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
110 raise types.ConnectionDeny( |
1216 | 111 types.ConnectionDeny.FORBIDDEN, "Bad token, please reload page" |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
112 ) |
995 | 113 self.token = token |
1203
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
114 token_map = self.tokens_map.pop(token) |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
115 self.page = token_map["page"] |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
116 self.request = WebsocketRequest(self, request, token_map["request"]) |
995 | 117 return protocol |
118 | |
119 def onOpen(self): | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
120 log.debug( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
121 _( |
1216 | 122 "Websocket opened for {page} (token: {token})".format( |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
123 page=self.page, token=self.token |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
124 ) |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
125 ) |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
126 ) |
995 | 127 self.page.onSocketOpen(self.request) |
128 | |
129 def onMessage(self, payload, isBinary): | |
130 try: | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
131 data_json = json.loads(payload.decode("utf8")) |
995 | 132 except ValueError as e: |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
133 log.warning( |
1216 | 134 _("Not valid JSON, ignoring data: {msg}\n{data}").format( |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
135 msg=e, data=payload |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
136 ) |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
137 ) |
995 | 138 return |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
139 # we request page first, to raise an AttributeError |
995 | 140 # if it is not set (which should never happen) |
141 page = self.page | |
142 try: | |
143 cb = page.on_data | |
144 except AttributeError: | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
145 log.warning( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
146 _( |
1216 | 147 'No "on_data" method set on dynamic page, ignoring data:\n{data}' |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
148 ).format(data=data_json) |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
149 ) |
995 | 150 else: |
151 cb(page, self.request, data_json) | |
152 | |
153 def onClose(self, wasClean, code, reason): | |
1206
d2e2bf14f2e2
server (websocket): don't crash if self.page doesn't exist while onClose is called
Goffi <goffi@goffi.org>
parents:
1203
diff
changeset
|
154 try: |
d2e2bf14f2e2
server (websocket): don't crash if self.page doesn't exist while onClose is called
Goffi <goffi@goffi.org>
parents:
1203
diff
changeset
|
155 page = self.page |
d2e2bf14f2e2
server (websocket): don't crash if self.page doesn't exist while onClose is called
Goffi <goffi@goffi.org>
parents:
1203
diff
changeset
|
156 except AttributeError: |
d2e2bf14f2e2
server (websocket): don't crash if self.page doesn't exist while onClose is called
Goffi <goffi@goffi.org>
parents:
1203
diff
changeset
|
157 log.debug( |
1216 | 158 "page is not available, the socket was probably not opened cleanly.\n" |
159 "reason: {reason}".format(reason=reason)) | |
1206
d2e2bf14f2e2
server (websocket): don't crash if self.page doesn't exist while onClose is called
Goffi <goffi@goffi.org>
parents:
1203
diff
changeset
|
160 return |
d2e2bf14f2e2
server (websocket): don't crash if self.page doesn't exist while onClose is called
Goffi <goffi@goffi.org>
parents:
1203
diff
changeset
|
161 page.onSocketClose(self.request) |
995 | 162 |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
163 log.debug( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
164 _( |
1216 | 165 "Websocket closed for {page} (token: {token}). {reason}".format( |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
166 page=self.page, |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
167 token=self.token, |
1216 | 168 reason="" |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
169 if wasClean |
1216 | 170 else _("Reason: {reason}").format(reason=reason), |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
171 ) |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
172 ) |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
173 ) |
995 | 174 |
175 @classmethod | |
176 def getBaseURL(cls, host, secure): | |
1216 | 177 return "ws{sec}://localhost:{port}".format( |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
178 sec="s" if secure else "", |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
179 port=cls.host.options["port_https" if secure else "port"], |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
180 ) |
995 | 181 |
182 @classmethod | |
183 def getResource(cls, host, secure): | |
184 if cls.host is None: | |
185 cls.host = host | |
186 factory = websocket.WebSocketServerFactory(cls.getBaseURL(host, secure)) | |
187 factory.protocol = cls | |
188 return resource.WebSocketResource(factory) | |
189 | |
190 @classmethod | |
191 def registerToken(cls, token, page, request): | |
192 if token in cls.tokens_map: | |
1216 | 193 raise exceptions.ConflictError(_("This token is already registered")) |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
194 cls.tokens_map[token] = {"page": page, "request": request} |