Mercurial > libervia-web
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) |