comparison libervia.tac @ 44:2744dd31e8a5

server side: Session management refactoring - Session is now managed in a more twisted-like way - Session time out managed - Session can now be locked, preventing for expiration - Session is locked when the getSignal request is active, preventing from expiration when the user has the page open but do nothing
author Goffi <goffi@goffi.org>
date Wed, 25 May 2011 14:24:41 +0200
parents 7782a786b2f0
children 7f106052326f
comparison
equal deleted inserted replaced
43:a7ff1e6f1229 44:2744dd31e8a5
26 26
27 from twisted.web import server 27 from twisted.web import server
28 from twisted.web import error as weberror 28 from twisted.web import error as weberror
29 from twisted.web.static import File 29 from twisted.web.static import File
30 from twisted.web.resource import Resource 30 from twisted.web.resource import Resource
31 from twisted.python.components import registerAdapter
31 from twisted.words.protocols.jabber.jid import JID 32 from twisted.words.protocols.jabber.jid import JID
32 from txjsonrpc.web import jsonrpc 33 from txjsonrpc.web import jsonrpc
33 from txjsonrpc import jsonrpclib 34 from txjsonrpc import jsonrpclib
34 from sat_frontends.bridge.DBus import DBusBridgeFrontend,BridgeExceptionNoService 35 from sat_frontends.bridge.DBus import DBusBridgeFrontend,BridgeExceptionNoService
35 import re 36 import re
36 import glob 37 import glob
37 import os.path 38 import os.path
39 import sys
38 from server_side.blog import MicroBlog 40 from server_side.blog import MicroBlog
39 41 from zope.interface import Interface, Attribute, implements
40 TIMEOUT = 120 #Session's time out, after that the user will be disconnected 42
43 TIMEOUT = 10 #Session's time out, after that the user will be disconnected
41 LIBERVIA_DIR = "output/" 44 LIBERVIA_DIR = "output/"
42 CARDS_DIR = "cards/" 45 CARDS_DIR = "cards/"
46
47 class ISATSession(Interface):
48 profile = Attribute("Sat profile")
49 jid = Attribute("JID associated with the profile")
50
51 class SATSession(object):
52 implements(ISATSession)
53 def __init__(self, session):
54 self.profile = None
55 self.jid = None
56
57 class LiberviaSession(server.Session):
58 sessionTimeout = TIMEOUT
59
60 def __init__(self, *args, **kwargs):
61 self.__lock = False
62 server.Session.__init__(self, *args, **kwargs)
63
64 def lock(self):
65 """Prevent session from expiring"""
66 self.__lock = True
67 self._expireCall.reset(sys.maxint)
68
69 def unlock(self):
70 """Allow session to expire again, and touch it"""
71 self.__lock = False
72 self.touch()
73
74 def touch(self):
75 if not self.__lock:
76 server.Session.touch(self)
77
43 78
44 class MethodHandler(jsonrpc.JSONRPC): 79 class MethodHandler(jsonrpc.JSONRPC):
45 80
46 def __init__(self, sat_host): 81 def __init__(self, sat_host):
47 jsonrpc.JSONRPC.__init__(self) 82 jsonrpc.JSONRPC.__init__(self)
48 self.sat_host=sat_host 83 self.sat_host=sat_host
49 84
50 def render(self, request): 85 def render(self, request):
51 self.session = request.getSession() 86 self.session = request.getSession()
52 try: 87 profile = ISATSession(self.session).profile
53 profile = self.session.sat_profile 88 if not profile:
54 except AttributeError:
55 #user is not identified, we return a jsonrpc fault 89 #user is not identified, we return a jsonrpc fault
56 parsed = jsonrpclib.loads(request.content.read()) 90 parsed = jsonrpclib.loads(request.content.read())
57 fault = jsonrpclib.Fault(0, "Not allowed") #FIXME: define some standard error codes for libervia 91 fault = jsonrpclib.Fault(0, "Not allowed") #FIXME: define some standard error codes for libervia
58 return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc')) 92 return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc'))
59 return jsonrpc.JSONRPC.render(self, request) 93 return jsonrpc.JSONRPC.render(self, request)
60 94
61 def jsonrpc_getProfileJid(self): 95 def jsonrpc_getProfileJid(self):
62 """Return the jid of the profile""" 96 """Return the jid of the profile"""
63 profile = self.session.sat_profile 97 sat_session = ISATSession(self.session)
64 self.session.sat_jid = self.sat_host.bridge.getParamA("JabberID", "Connection", profile_key=profile) 98 profile = sat_session.profile
65 return self.session.sat_jid 99 sat_session.sat_jid = JID(self.sat_host.bridge.getParamA("JabberID", "Connection", profile_key=profile))
100 return sat_session.sat_jid.full()
66 101
67 def jsonrpc_getContacts(self): 102 def jsonrpc_getContacts(self):
68 """Return all passed args.""" 103 """Return all passed args."""
69 profile = self.session.sat_profile 104 profile = ISATSession(self.session).profile
70 return self.sat_host.bridge.getContacts(profile) 105 return self.sat_host.bridge.getContacts(profile)
71 106
72 def jsonrpc_setStatus(self, status): 107 def jsonrpc_setStatus(self, status):
73 """Change the status""" 108 """Change the status"""
74 profile = self.session.sat_profile 109 profile = ISATSession(self.session).profile
75 print "new status received:", status 110 print "new status received:", status
76 self.sat_host.bridge.setPresence('', '', 0, {'':status}, profile) 111 self.sat_host.bridge.setPresence('', '', 0, {'':status}, profile)
77 112
78 113
79 def jsonrpc_sendMessage(self, to_jid, msg, subject, type): 114 def jsonrpc_sendMessage(self, to_jid, msg, subject, type):
80 """send message""" 115 """send message"""
81 profile = self.session.sat_profile 116 profile = ISATSession(self.session).profile
82 return self.sat_host.bridge.sendMessage(to_jid, msg, subject, type, profile) 117 return self.sat_host.bridge.sendMessage(to_jid, msg, subject, type, profile)
83 118
84 def jsonrpc_sendMblog(self, raw_text): 119 def jsonrpc_sendMblog(self, raw_text):
85 """Parse raw_text of the microblog box, and send message consequently""" 120 """Parse raw_text of the microblog box, and send message consequently"""
86 profile = self.session.sat_profile 121 profile = ISATSession(self.session).profile
87 match = re.match(r'@(.+?): *(.*$)', raw_text) 122 match = re.match(r'@(.+?): *(.*$)', raw_text)
88 if match: 123 if match:
89 recip = match.group(1) 124 recip = match.group(1)
90 text = match.group(2) 125 text = match.group(2)
91 if recip == '@' and text: 126 if recip == '@' and text:
95 else: 130 else:
96 return self.sat_host.bridge.sendGroupBlog([recip], text, profile) 131 return self.sat_host.bridge.sendGroupBlog([recip], text, profile)
97 132
98 def jsonrpc_getPresenceStatus(self): 133 def jsonrpc_getPresenceStatus(self):
99 """Get Presence information for connected contacts""" 134 """Get Presence information for connected contacts"""
100 profile = self.session.sat_profile 135 profile = ISATSession(self.session).profile
101 return self.sat_host.bridge.getPresenceStatus(profile) 136 return self.sat_host.bridge.getPresenceStatus(profile)
102 137
103 def jsonrpc_getHistory(self, from_jid, to_jid, size): 138 def jsonrpc_getHistory(self, from_jid, to_jid, size):
104 """Return history for the from_jid/to_jid couple""" 139 """Return history for the from_jid/to_jid couple"""
105 #FIXME: this method should definitely be asynchrone, need to fix it !!! 140 #FIXME: this method should definitely be asynchrone, need to fix it !!!
106 profile = self.session.sat_profile 141 sat_session = ISATSession(self.session)
107 try: 142 profile = sat_session.profile
108 _jid = JID(self.session.sat_jid) 143 sat_jid = sat_session.jid
109 except: 144 if not sat_jid:
110 error("No jid saved for this profile") 145 error("No jid saved for this profile")
111 return {} 146 return {}
112 if JID(from_jid).userhost() != _jid.userhost() and JID(to_jid) != _jid.userhost(): 147 if JID(from_jid).userhost() != sat_jid.userhost() and JID(to_jid).userhost() != sat_jid.userhost():
113 error("Trying to get history from a different jid, maybe a hack attempt ?") 148 error("Trying to get history from a different jid, maybe a hack attempt ?")
114 return {} 149 return {}
115 return self.sat_host.bridge.getHistory(from_jid, to_jid, size) 150 return self.sat_host.bridge.getHistory(from_jid, to_jid, size)
116 151
117 def jsonrpc_getRoomJoined(self): 152 def jsonrpc_getRoomJoined(self):
118 """Return list of room already joined by user""" 153 """Return list of room already joined by user"""
119 profile = self.session.sat_profile 154 profile = ISATSession(self.session).profile
120 return self.sat_host.bridge.getRoomJoined(profile) 155 return self.sat_host.bridge.getRoomJoined(profile)
121 156
122 def jsonrpc_launchTarotGame(self, other_players): 157 def jsonrpc_launchTarotGame(self, other_players):
123 """Create a room, invite the other players and start a Tarot game""" 158 """Create a room, invite the other players and start a Tarot game"""
124 profile = self.session.sat_profile 159 profile = ISATSession(self.session).profile
125 self.sat_host.bridge.tarotGameLaunch(other_players, profile) 160 self.sat_host.bridge.tarotGameLaunch(other_players, profile)
126 161
127 def jsonrpc_getTarotCardsPaths(self): 162 def jsonrpc_getTarotCardsPaths(self):
128 """Give the path of all the tarot cards""" 163 """Give the path of all the tarot cards"""
129 return map(lambda x: x[len(LIBERVIA_DIR):],glob.glob(os.path.join(LIBERVIA_DIR,CARDS_DIR,'*_*.png'))); 164 return map(lambda x: x[len(LIBERVIA_DIR):],glob.glob(os.path.join(LIBERVIA_DIR,CARDS_DIR,'*_*.png')));
130 165
131 def jsonrpc_tarotGameReady(self, player, referee): 166 def jsonrpc_tarotGameReady(self, player, referee):
132 """Tell to the server that we are ready to start the game""" 167 """Tell to the server that we are ready to start the game"""
133 profile = self.session.sat_profile 168 profile = ISATSession(self.session).profile
134 self.sat_host.bridge.tarotGameReady(player, referee) 169 self.sat_host.bridge.tarotGameReady(player, referee)
135 170
136 def jsonrpc_tarotGameContratChoosed(self, player_nick, referee, contrat): 171 def jsonrpc_tarotGameContratChoosed(self, player_nick, referee, contrat):
137 """Tell to the server that we are ready to start the game""" 172 """Tell to the server that we are ready to start the game"""
138 profile = self.session.sat_profile 173 profile = ISATSession(self.session).profile
139 self.sat_host.bridge.tarotGameContratChoosed(player_nick, referee, contrat, profile) 174 self.sat_host.bridge.tarotGameContratChoosed(player_nick, referee, contrat, profile)
140 175
141 def jsonrpc_tarotGamePlayCards(self, player_nick, referee, cards): 176 def jsonrpc_tarotGamePlayCards(self, player_nick, referee, cards):
142 """Tell to the server that we are ready to start the game""" 177 """Tell to the server that we are ready to start the game"""
143 profile = self.session.sat_profile 178 profile = ISATSession(self.session).profile
144 self.sat_host.bridge.tarotGamePlayCards(player_nick, referee, cards, profile) 179 self.sat_host.bridge.tarotGamePlayCards(player_nick, referee, cards, profile)
145 180
146 class Register(jsonrpc.JSONRPC): 181 class Register(jsonrpc.JSONRPC):
147 """This class manage the registration procedure with SàT 182 """This class manage the registration procedure with SàT
148 It provide an api for the browser, check password and setup the web server""" 183 It provide an api for the browser, check password and setup the web server"""
160 else: 195 else:
161 return None 196 return None
162 197
163 def _fillMblogNodes(self, result, session): 198 def _fillMblogNodes(self, result, session):
164 """Fill the microblog nodes association for this session""" 199 """Fill the microblog nodes association for this session"""
165 print "Filling session for %s with %s" % (session.sat_profile, result)
166 session.sat_mblog_nodes = dict(result) 200 session.sat_mblog_nodes = dict(result)
167 201
168 def render(self, request): 202 def render(self, request):
169 """ 203 """
170 Render method with some hacks: 204 Render method with some hacks:
176 return self.login(request) 210 return self.login(request)
177 _session = request.getSession() 211 _session = request.getSession()
178 parsed = jsonrpclib.loads(request.content.read()) 212 parsed = jsonrpclib.loads(request.content.read())
179 if parsed.get("method")!="isRegistered": 213 if parsed.get("method")!="isRegistered":
180 #if we don't call login or isRegistered, we need to be identified 214 #if we don't call login or isRegistered, we need to be identified
181 try: 215 profile = ISATSession(_session).profile
182 profile = _session.sat_profile 216 if not profile:
183 except AttributeError:
184 #user is not identified, we return a jsonrpc fault 217 #user is not identified, we return a jsonrpc fault
185 fault = jsonrpclib.Fault(0, "Not allowed") #FIXME: define some standard error codes for libervia 218 fault = jsonrpclib.Fault(0, "Not allowed") #FIXME: define some standard error codes for libervia
186 return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc')) 219 return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc'))
187 self.request = request 220 self.request = request
188 return jsonrpc.JSONRPC.render(self, request) 221 return jsonrpc.JSONRPC.render(self, request)
233 def _logged(self, profile, request, finish=True): 266 def _logged(self, profile, request, finish=True):
234 """Set everything when a user just logged 267 """Set everything when a user just logged
235 and return "LOGGED" to the requester""" 268 and return "LOGGED" to the requester"""
236 self.__cleanWaiting(profile) 269 self.__cleanWaiting(profile)
237 _session = request.getSession() 270 _session = request.getSession()
238 _session.sat_profile = profile 271 sat_session = ISATSession(_session)
272 if sat_session.profile:
273 error (_('/!\\ Session has already a profile, this should NEVER happen !'))
274 sat_session.profile = profile
239 self.sat_host.prof_connected.add(profile) 275 self.sat_host.prof_connected.add(profile)
240 d = defer.Deferred() 276 d = defer.Deferred()
241 self.sat_host.bridge.getMblogNodes(profile, d.callback, d.errback) 277 self.sat_host.bridge.getMblogNodes(profile, d.callback, d.errback)
242 d.addCallback(self._fillMblogNodes, _session) 278 d.addCallback(self._fillMblogNodes, _session)
243 if finish: 279 if finish:
251 self.__cleanWaiting(login) 287 self.__cleanWaiting(login)
252 return error_type 288 return error_type
253 289
254 def jsonrpc_isConnected(self): 290 def jsonrpc_isConnected(self):
255 _session = self.request.getSession() 291 _session = self.request.getSession()
256 profile = _session.sat_profile 292 profile = ISATSession(_session).profile
257 return self.sat_host.bridge.isConnected(profile) 293 return self.sat_host.bridge.isConnected(profile)
258 294
259 def jsonrpc_connect(self): 295 def jsonrpc_connect(self):
260 _session = self.request.getSession() 296 _session = self.request.getSession()
261 profile = _session.sat_profile 297 profile = ISATSession(_session).profile
262 if self.profiles_waiting.has_key(profile): 298 if self.profiles_waiting.has_key(profile):
263 raise jsonrpclib.Fault('1','Already waiting') #FIXME: define some standard error codes for libervia 299 raise jsonrpclib.Fault('1','Already waiting') #FIXME: define some standard error codes for libervia
264 self.profiles_waiting[profile] = self.request 300 self.profiles_waiting[profile] = self.request
265 self.sat_host.bridge.connect(profile) 301 self.sat_host.bridge.connect(profile)
266 return server.NOT_DONE_YET 302 return server.NOT_DONE_YET
267 303
268 def jsonrpc_isRegistered(self): 304 def jsonrpc_isRegistered(self):
269 """Tell if the user is already registered""" 305 """Tell if the user is already registered"""
270 _session = self.request.getSession() 306 _session = self.request.getSession()
271 try: 307 profile = ISATSession(_session).profile
272 profile = _session.sat_profile 308 return bool(profile)
273 except AttributeError:
274 return False
275 return True
276 309
277 class SignalHandler(jsonrpc.JSONRPC): 310 class SignalHandler(jsonrpc.JSONRPC):
278 311
279 def __init__(self, sat_host): 312 def __init__(self, sat_host):
280 Resource.__init__(self) 313 Resource.__init__(self)
288 321
289 def jsonrpc_getSignals(self): 322 def jsonrpc_getSignals(self):
290 """Keep the connection alive until a signal is received, then send it 323 """Keep the connection alive until a signal is received, then send it
291 @return: (signal, *signal_args)""" 324 @return: (signal, *signal_args)"""
292 _session = self.request.getSession() 325 _session = self.request.getSession()
293 profile = _session.sat_profile 326 profile = ISATSession(_session).profile
294 if profile in self.queue: #if we have signals to send in queue 327 if profile in self.queue: #if we have signals to send in queue
295 if self.queue[profile]: 328 if self.queue[profile]:
296 return self.queue[profile].pop(0) 329 return self.queue[profile].pop(0)
297 else: 330 else:
298 #the queue is empty, we delete the profile from queue 331 #the queue is empty, we delete the profile from queue
299 del self.queue[profile] 332 del self.queue[profile]
333 _session.lock() #we don't want the session to expire as long as this connection is active
334 def unlock(ignore):
335 _session.unlock()
300 self.signalDeferred[profile] = defer.Deferred() 336 self.signalDeferred[profile] = defer.Deferred()
337 self.request.notifyFinish().addBoth(unlock)
301 return self.signalDeferred[profile] 338 return self.signalDeferred[profile]
302 339
303 def getGenericCb(self, function_name): 340 def getGenericCb(self, function_name):
304 """Return a generic function which send all params to signalDeferred.callback 341 """Return a generic function which send all params to signalDeferred.callback
305 function must have profile as last argument""" 342 function must have profile as last argument"""
336 """ 373 """
337 Render method wich reject access if user is not identified 374 Render method wich reject access if user is not identified
338 """ 375 """
339 _session = request.getSession() 376 _session = request.getSession()
340 parsed = jsonrpclib.loads(request.content.read()) 377 parsed = jsonrpclib.loads(request.content.read())
341 try: 378 profile = ISATSession(_session).profile
342 profile = _session.sat_profile 379 if not profile:
343 except AttributeError:
344 #user is not identified, we return a jsonrpc fault 380 #user is not identified, we return a jsonrpc fault
345 fault = jsonrpclib.Fault(0, "Not allowed") #FIXME: define some standard error codes for libervia 381 fault = jsonrpclib.Fault(0, "Not allowed") #FIXME: define some standard error codes for libervia
346 return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc')) 382 return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc'))
347 self.request = request 383 self.request = request
348 return jsonrpc.JSONRPC.render(self, request) 384 return jsonrpc.JSONRPC.render(self, request)
360 ## bridge ## 396 ## bridge ##
361 try: 397 try:
362 self.bridge=DBusBridgeFrontend() 398 self.bridge=DBusBridgeFrontend()
363 except BridgeExceptionNoService: 399 except BridgeExceptionNoService:
364 print(u"Can't connect to SàT backend, are you sure it's launched ?") 400 print(u"Can't connect to SàT backend, are you sure it's launched ?")
365 import sys
366 sys.exit(1) 401 sys.exit(1)
367 self.bridge.register("connected", self.signal_handler.connected) 402 self.bridge.register("connected", self.signal_handler.connected)
368 self.bridge.register("connectionError", self.signal_handler.connectionError) 403 self.bridge.register("connectionError", self.signal_handler.connectionError)
369 for signal_name in ['presenceUpdate', 'personalEvent', 'newMessage', 'roomJoined', 'roomUserJoined', 'roomUserLeft', 'tarotGameStarted', 'tarotGameNew', 404 for signal_name in ['presenceUpdate', 'personalEvent', 'newMessage', 'roomJoined', 'roomUserJoined', 'roomUserLeft', 'tarotGameStarted', 'tarotGameNew',
370 'tarotGameChooseContrat', 'tarotGameShowCards', 'tarotGameInvalidCards', 'tarotGameCardsPlayed', 'tarotGameYourTurn', 'tarotGameScore']: 405 'tarotGameChooseContrat', 'tarotGameShowCards', 'tarotGameInvalidCards', 'tarotGameCardsPlayed', 'tarotGameYourTurn', 'tarotGameScore']:
373 root.putChild('json_api', MethodHandler(self)) 408 root.putChild('json_api', MethodHandler(self))
374 root.putChild('register_api', _register) 409 root.putChild('register_api', _register)
375 root.putChild('blog', MicroBlog(self)) 410 root.putChild('blog', MicroBlog(self))
376 root.putChild('css', File("server_css/")) 411 root.putChild('css', File("server_css/"))
377 self.site = server.Site(root) 412 self.site = server.Site(root)
413 self.site.sessionFactory = LiberviaSession
378 414
379 def startService(self): 415 def startService(self):
380 reactor.listenTCP(8080, self.site) 416 reactor.listenTCP(8080, self.site)
381 417
382 def run(self): 418 def run(self):
384 420
385 def stop(self): 421 def stop(self):
386 reactor.stop() 422 reactor.stop()
387 423
388 424
389 425 registerAdapter(SATSession, server.Session, ISATSession)
390 application = service.Application('Libervia') 426 application = service.Application('Libervia')
391 service = Libervia() 427 service = Libervia()
392 service.setServiceParent(application) 428 service.setServiceParent(application)