comparison libervia/server/server.py @ 1504:409d10211b20

server, browser: dynamic pages refactoring: dynamic pages has been reworked, to change the initial basic implementation. Pages are now dynamic by default, and a websocket is established by the first connected page of a session. The socket is used to transmit bridge signals, and then the signal is broadcasted to other tabs using broadcast channel. If the connecting tab is closed, an other one is chosen. Some tests are made to retry connecting in case of problem, and sometimes reload the pages (e.g. if profile is connected). Signals (or other data) are cached during reconnection phase, to avoid lost of data. All previous partial rendering mechanism have been removed, chat page is temporarily not working anymore, but will be eventually redone (one of the goal of this work is to have proper chat).
author Goffi <goffi@goffi.org>
date Wed, 01 Mar 2023 18:02:44 +0100
parents 6643855770a5
children a169cbc315f0
comparison
equal deleted inserted replaced
1503:2796e73ed50c 1504:409d10211b20
842 class Libervia(service.Service): 842 class Libervia(service.Service):
843 debug = defer.Deferred.debug # True if twistd/Libervia is launched in debug mode 843 debug = defer.Deferred.debug # True if twistd/Libervia is launched in debug mode
844 844
845 def __init__(self, options): 845 def __init__(self, options):
846 self.options = options 846 self.options = options
847 websockets.host = self
847 848
848 def _init(self): 849 def _init(self):
849 # we do init here and not in __init__ to avoid doule initialisation with twistd 850 # we do init here and not in __init__ to avoid doule initialisation with twistd
850 # this _init is called in startService 851 # this _init is called in startService
851 self.initialised = defer.Deferred() 852 self.initialised = defer.Deferred()
1197 d.addCallback(self._namespacesGetCb) 1198 d.addCallback(self._namespacesGetCb)
1198 d.addErrback(self._namespacesGetEb) 1199 d.addErrback(self._namespacesGetEb)
1199 1200
1200 # websocket 1201 # websocket
1201 if self.options["connection_type"] in ("https", "both"): 1202 if self.options["connection_type"] in ("https", "both"):
1202 wss = websockets.LiberviaPageWSProtocol.getResource(self, secure=True) 1203 wss = websockets.LiberviaPageWSProtocol.getResource(secure=True)
1203 self.putChildAll(b'wss', wss) 1204 self.putChildAll(b'wss', wss)
1204 if self.options["connection_type"] in ("http", "both"): 1205 if self.options["connection_type"] in ("http", "both"):
1205 ws = websockets.LiberviaPageWSProtocol.getResource(self, secure=False) 1206 ws = websockets.LiberviaPageWSProtocol.getResource(secure=False)
1206 self.putChildAll(b'ws', ws) 1207 self.putChildAll(b'ws', ws)
1207 1208
1208 ## following signal is needed for cache handling in Libervia pages 1209 ## following signal is needed for cache handling in Libervia pages
1209 self.bridge.register_signal( 1210 self.bridge.register_signal(
1210 "psEventRaw", partial(LiberviaPage.onNodeEvent, self), "plugin" 1211 "psEventRaw", partial(LiberviaPage.onNodeEvent, self), "plugin"
1211 ) 1212 )
1212 self.bridge.register_signal( 1213 self.bridge.register_signal(
1213 "messageNew", partial(LiberviaPage.onSignal, self, "messageNew") 1214 "messageNew", partial(self.on_signal, "messageNew")
1214 ) 1215 )
1215 1216
1216 #  Progress handling 1217 #  Progress handling
1217 self.bridge.register_signal( 1218 self.bridge.register_signal(
1218 "progressStarted", partial(ProgressHandler._signal, "started") 1219 "progressStarted", partial(ProgressHandler._signal, "started")
1323 kwargs["callback"] = _callback 1324 kwargs["callback"] = _callback
1324 kwargs["errback"] = _errback 1325 kwargs["errback"] = _errback
1325 getattr(self.bridge, method_name)(*args, **kwargs) 1326 getattr(self.bridge, method_name)(*args, **kwargs)
1326 return d 1327 return d
1327 1328
1329 def on_signal(self, signal_name, *args):
1330 profile = args[-1]
1331 if not profile:
1332 log.error(f"got signal without profile: {signal_name}, {args}")
1333 return
1334 try:
1335 sockets = websockets.LiberviaPageWSProtocol.profile_map[profile]
1336 except KeyError:
1337 log.debug(f"no socket opened for profile {profile}")
1338 return
1339 for socket in sockets:
1340 socket.send("bridge", {"signal": signal_name, "args": args})
1341
1328 async def _logged(self, profile, request): 1342 async def _logged(self, profile, request):
1329 """Set everything when a user just logged in 1343 """Set everything when a user just logged in
1330 1344
1331 @param profile 1345 @param profile
1332 @param request 1346 @param request
1358 _("profile cache resource added from {uuid} to {path}").format( 1372 _("profile cache resource added from {uuid} to {path}").format(
1359 uuid=sat_session.uuid, path=cache_dir 1373 uuid=sat_session.uuid, path=cache_dir
1360 ) 1374 )
1361 ) 1375 )
1362 1376
1363 def onExpire(): 1377 def on_expire():
1364 log.info("Session expired (profile={profile})".format(profile=profile)) 1378 log.info("Session expired (profile={profile})".format(profile=profile))
1365 self.cache_resource.delEntity(sat_session.uuid.encode('utf-8')) 1379 self.cache_resource.delEntity(sat_session.uuid.encode('utf-8'))
1366 log.debug( 1380 log.debug(
1367 _("profile cache resource {uuid} deleted").format(uuid=sat_session.uuid) 1381 _("profile cache resource {uuid} deleted").format(uuid=sat_session.uuid)
1368 ) 1382 )
1383 sat_session.on_expire()
1384 if sat_session.ws_socket is not None:
1385 sat_session.ws_socket.close()
1369 # and now we disconnect the profile 1386 # and now we disconnect the profile
1370 self.bridgeCall("disconnect", profile) 1387 self.bridgeCall("disconnect", profile)
1371 1388
1372 session.notifyOnExpire(onExpire) 1389 session.notifyOnExpire(on_expire)
1373 1390
1374 # FIXME: those session infos should be returned by connect or isConnected 1391 # FIXME: those session infos should be returned by connect or isConnected
1375 infos = await self.bridgeCall("sessionInfosGet", profile) 1392 infos = await self.bridgeCall("sessionInfosGet", profile)
1376 sat_session.jid = jid.JID(infos["jid"]) 1393 sat_session.jid = jid.JID(infos["jid"])
1377 own_bare_jid_s = sat_session.jid.userhost() 1394 own_bare_jid_s = sat_session.jid.userhost()
1766 def purgeSession(self, request): 1783 def purgeSession(self, request):
1767 """helper method to purge a session during request handling""" 1784 """helper method to purge a session during request handling"""
1768 session = request.session 1785 session = request.session
1769 if session is not None: 1786 if session is not None:
1770 log.debug(_("session purge")) 1787 log.debug(_("session purge"))
1788 sat_session = self.getSessionData(request, session_iface.ISATSession)
1789 socket = sat_session.ws_socket
1790 if socket is not None:
1791 socket.close()
1792 session.ws_socket = None
1771 session.expire() 1793 session.expire()
1772 # FIXME: not clean but it seems that it's the best way to reset 1794 # FIXME: not clean but it seems that it's the best way to reset
1773 # session during request handling 1795 # session during request handling
1774 request._secureSession = request._insecureSession = None 1796 request._secureSession = request._insecureSession = None
1775 1797
1822 sat_session.setAffiliation(service, node, affiliation) 1844 sat_session.setAffiliation(service, node, affiliation)
1823 defer.returnValue(affiliation) 1845 defer.returnValue(affiliation)
1824 1846
1825 ## Websocket (dynamic pages) ## 1847 ## Websocket (dynamic pages) ##
1826 1848
1827 def getWebsocketURL(self, request): 1849 def get_websocket_url(self, request):
1828 base_url_split = self.getExtBaseURLData(request) 1850 base_url_split = self.getExtBaseURLData(request)
1829 if base_url_split.scheme.endswith("s"): 1851 if base_url_split.scheme.endswith("s"):
1830 scheme = "wss" 1852 scheme = "wss"
1831 else: 1853 else:
1832 scheme = "ws" 1854 scheme = "ws"
1833 1855
1834 return self.getExtBaseURL(request, path=scheme, scheme=scheme) 1856 return self.getExtBaseURL(request, path=scheme, scheme=scheme)
1835 1857
1836 def registerWSToken(self, token, page, request):
1837 # we make a shallow copy of request to avoid losing request.channel when
1838 # connection is lost (which would result as request.isSecure() being always
1839 # False). See #327
1840 request._signal_id = id(request)
1841 websockets.LiberviaPageWSProtocol.registerToken(token, page, copy.copy(request))
1842 1858
1843 ## Various utils ## 1859 ## Various utils ##
1844 1860
1845 def getHTTPDate(self, timestamp=None): 1861 def getHTTPDate(self, timestamp=None):
1846 now = time.gmtime(timestamp) 1862 now = time.gmtime(timestamp)