Getting an improved version of the shared session system vaguely in shape.
This commit is contained in:
parent
a31441b3ce
commit
eebd41f46d
11 changed files with 143 additions and 130 deletions
|
|
@ -27,7 +27,6 @@ from evennia.players.models import PlayerDB
|
|||
from evennia.utils.logger import log_err
|
||||
from evennia.utils.utils import to_str, to_unicode
|
||||
|
||||
# django browser sessions
|
||||
BrowserSessionStore = importlib.import_module(settings.SESSION_ENGINE).SessionStore
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ class PortalSessionHandler(SessionHandler):
|
|||
sessdata = dict((key, val) for key, val in sessdata.items() if key in ("protocol_key",
|
||||
"address",
|
||||
"sessid",
|
||||
"suid",
|
||||
"csessid",
|
||||
"conn_time",
|
||||
"protocol_flags",
|
||||
"server_data",))
|
||||
|
|
@ -274,20 +274,20 @@ class PortalSessionHandler(SessionHandler):
|
|||
"""
|
||||
return len(self.get_sessions(include_unloggedin=include_unloggedin))
|
||||
|
||||
def session_from_suid(self, suid):
|
||||
def session_from_csessid(self, csessid):
|
||||
"""
|
||||
Given a session id, retrieve the session (this is primarily
|
||||
intended to be called by web clients)
|
||||
|
||||
Args:
|
||||
suid (int): Session id.
|
||||
csessid (int): Session id.
|
||||
|
||||
Returns:
|
||||
session (list): The matching session, if found.
|
||||
|
||||
"""
|
||||
return [sess for sess in self.get_sessions(include_unloggedin=True)
|
||||
if hasattr(sess, 'suid') and sess.suid == suid]
|
||||
if hasattr(sess, 'csessid') and sess.csessid == csessid]
|
||||
|
||||
def announce_all(self, message):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -32,11 +32,12 @@ import json
|
|||
from twisted.internet.protocol import Protocol
|
||||
from django.conf import settings
|
||||
from evennia.server.session import Session
|
||||
from evennia.utils.utils import to_str
|
||||
from evennia.utils.utils import to_str, mod_import
|
||||
from evennia.utils.ansi import parse_ansi
|
||||
from evennia.utils.text2html import parse_html
|
||||
|
||||
_RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE)
|
||||
_CLIENT_SESSIONS = mod_import(settings.SESSION_ENGINE).SessionStore
|
||||
|
||||
|
||||
class WebSocketClient(Protocol, Session):
|
||||
|
|
@ -52,6 +53,8 @@ class WebSocketClient(Protocol, Session):
|
|||
self.transport.validationMade = self.validationMade
|
||||
client_address = self.transport.client
|
||||
client_address = client_address[0] if client_address else None
|
||||
print ("connectionMade: webclient address", client_address, self.transport.client, self.transport.client.__dict__, self.transport.__dict__)
|
||||
|
||||
self.init_session("websocket", client_address, self.factory.sessionhandler)
|
||||
# watch for dead links
|
||||
self.transport.setTcpKeepAlive(1)
|
||||
|
|
@ -123,15 +126,36 @@ class WebSocketClient(Protocol, Session):
|
|||
kwargs (any): Options from protocol.
|
||||
|
||||
Notes:
|
||||
The websocket client can send the
|
||||
"websocket_close" command to report
|
||||
that the client has been closed and
|
||||
that the session should be disconnected.
|
||||
At initilization, the client will send the special
|
||||
'csessid' command to identify its browser session hash
|
||||
with the Evennia side.
|
||||
|
||||
The websocket client will also pass 'websocket_close' command
|
||||
to report that the client has been closed and that the
|
||||
session should be disconnected.
|
||||
|
||||
Both those commands are parsed and extracted already at
|
||||
this point.
|
||||
|
||||
"""
|
||||
|
||||
if "csessid" in kwargs and self.csessid is None:
|
||||
# only allow to change csessid on the very first connect
|
||||
# - this is a safety measure to avoid a clself.transport.client.__dict__, ient to manually
|
||||
# change its csessid later.
|
||||
self.csessid = kwargs.pop("csessid")
|
||||
csession = _CLIENT_SESSIONS(session_key=self.csessid)
|
||||
uid = csession and csession.get("logged_in", False)
|
||||
if uid:
|
||||
# the browser session is already logged in.
|
||||
self.uid = uid
|
||||
self.logged_in = True
|
||||
return
|
||||
|
||||
if "websocket_close" in kwargs:
|
||||
self.disconnect()
|
||||
return
|
||||
|
||||
self.sessionhandler.data_in(self, **kwargs)
|
||||
|
||||
def data_out(self, **kwargs):
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import json
|
|||
import re
|
||||
|
||||
from time import time
|
||||
from hashlib import md5
|
||||
from twisted.web import server, resource
|
||||
from twisted.internet.task import LoopingCall
|
||||
from django.utils.functional import Promise
|
||||
|
|
@ -71,10 +70,10 @@ class WebClient(resource.Resource):
|
|||
self.last_alive = {}
|
||||
self.keep_alive = None
|
||||
|
||||
def _responseFailed(self, failure, suid, request):
|
||||
def _responseFailed(self, failure, csessid, request):
|
||||
"callback if a request is lost/timed out"
|
||||
try:
|
||||
del self.requests[suid]
|
||||
del self.requests[csessid]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
|
@ -84,62 +83,62 @@ class WebClient(resource.Resource):
|
|||
"""
|
||||
now = time()
|
||||
to_remove = []
|
||||
keep_alives = ((suid, remove) for suid, (t, remove)
|
||||
keep_alives = ((csessid, remove) for csessid, (t, remove)
|
||||
in self.last_alive.iteritems() if now - t > _KEEPALIVE)
|
||||
for suid, remove in keep_alives:
|
||||
for csessid, remove in keep_alives:
|
||||
if remove:
|
||||
# keepalive timeout. Line is dead.
|
||||
to_remove.append(suid)
|
||||
to_remove.append(csessid)
|
||||
else:
|
||||
# normal timeout - send keepalive
|
||||
self.last_alive[suid] = (now, True)
|
||||
self.lineSend(suid, ["ajax_keepalive", [], {}])
|
||||
self.last_alive[csessid] = (now, True)
|
||||
self.lineSend(csessid, ["ajax_keepalive", [], {}])
|
||||
# remove timed-out sessions
|
||||
for suid in to_remove:
|
||||
sess = self.sessionhandler.session_from_suid(suid)
|
||||
for csessid in to_remove:
|
||||
sess = self.sessionhandler.sessions_from_csessid(csessid)
|
||||
if sess:
|
||||
sess[0].disconnect()
|
||||
self.last_alive.pop(suid, None)
|
||||
self.last_alive.pop(csessid, None)
|
||||
if not self.last_alive:
|
||||
# no more ajax clients. Stop the keepalive
|
||||
self.keep_alive.stop()
|
||||
self.keep_alive = None
|
||||
|
||||
def lineSend(self, suid, data):
|
||||
def lineSend(self, csessid, data):
|
||||
"""
|
||||
This adds the data to the buffer and/or sends it to the client
|
||||
as soon as possible.
|
||||
|
||||
Args:
|
||||
suid (int): Session id.
|
||||
csessid (int): Session id.
|
||||
data (list): A send structure [cmdname, [args], {kwargs}].
|
||||
|
||||
"""
|
||||
request = self.requests.get(suid)
|
||||
request = self.requests.get(csessid)
|
||||
if request:
|
||||
# we have a request waiting. Return immediately.
|
||||
request.write(jsonify(data))
|
||||
request.finish()
|
||||
del self.requests[suid]
|
||||
del self.requests[csessid]
|
||||
else:
|
||||
# no waiting request. Store data in buffer
|
||||
dataentries = self.databuffer.get(suid, [])
|
||||
dataentries = self.databuffer.get(csessid, [])
|
||||
dataentries.append(jsonify(data))
|
||||
self.databuffer[suid] = dataentries
|
||||
self.databuffer[csessid] = dataentries
|
||||
|
||||
def client_disconnect(self, suid):
|
||||
def client_disconnect(self, csessid):
|
||||
"""
|
||||
Disconnect session with given suid.
|
||||
Disconnect session with given csessid.
|
||||
|
||||
Args:
|
||||
suid (int): Session id.
|
||||
csessid (int): Session id.
|
||||
|
||||
"""
|
||||
if suid in self.requests:
|
||||
self.requests[suid].finish()
|
||||
del self.requests[suid]
|
||||
if suid in self.databuffer:
|
||||
del self.databuffer[suid]
|
||||
if csessid in self.requests:
|
||||
self.requests[csessid].finish()
|
||||
del self.requests[csessid]
|
||||
if csessid in self.databuffer:
|
||||
del self.databuffer[csessid]
|
||||
|
||||
def mode_init(self, request):
|
||||
"""
|
||||
|
|
@ -150,38 +149,32 @@ class WebClient(resource.Resource):
|
|||
request (Request): Incoming request.
|
||||
|
||||
"""
|
||||
suid = request.args.get('suid', ['0'])[0]
|
||||
csessid = request.args.get('csessid')
|
||||
|
||||
remote_addr = request.getClientIP()
|
||||
host_string = "%s (%s:%s)" % (_SERVERNAME, request.getRequestHostname(), request.getHost().port)
|
||||
if suid == '0':
|
||||
# creating a unique id hash string
|
||||
suid = md5(str(time())).hexdigest()
|
||||
self.databuffer[suid] = []
|
||||
|
||||
sess = WebClientSession()
|
||||
sess.client = self
|
||||
sess.init_session("ajax/comet", remote_addr, self.sessionhandler)
|
||||
sess.suid = suid
|
||||
sess.sessionhandler.connect(sess)
|
||||
sess = WebClientSession()
|
||||
sess.client = self
|
||||
sess.init_session("ajax/comet", remote_addr, self.sessionhandler)
|
||||
sess.csessid = csessid
|
||||
sess.sessionhandler.connect(sess)
|
||||
|
||||
self.last_alive[suid] = (time(), False)
|
||||
if not self.keep_alive:
|
||||
# the keepalive is not running; start it.
|
||||
self.keep_alive = LoopingCall(self._keepalive)
|
||||
self.keep_alive.start(_KEEPALIVE, now=False)
|
||||
self.last_alive[csessid] = (time(), False)
|
||||
if not self.keep_alive:
|
||||
# the keepalive is not running; start it.
|
||||
self.keep_alive = LoopingCall(self._keepalive)
|
||||
self.keep_alive.start(_KEEPALIVE, now=False)
|
||||
|
||||
return jsonify({'msg': host_string, 'suid': suid})
|
||||
return jsonify({'msg': host_string, 'csessid': csessid})
|
||||
|
||||
def mode_keepalive(self, request):
|
||||
"""
|
||||
This is called by render_POST when the
|
||||
client is replying to the keepalive.
|
||||
"""
|
||||
suid = request.args.get('suid', ['0'])[0]
|
||||
if suid == '0':
|
||||
return '""'
|
||||
self.last_alive[suid] = (time(), False)
|
||||
csessid = request.args.get('csessid')[0]
|
||||
self.last_alive[csessid] = (time(), False)
|
||||
return '""'
|
||||
|
||||
def mode_input(self, request):
|
||||
|
|
@ -193,12 +186,10 @@ class WebClient(resource.Resource):
|
|||
request (Request): Incoming request.
|
||||
|
||||
"""
|
||||
suid = request.args.get('suid', ['0'])[0]
|
||||
if suid == '0':
|
||||
return '""'
|
||||
csessid = request.args.get('csessid')[0]
|
||||
|
||||
self.last_alive[suid] = (time(), False)
|
||||
sess = self.sessionhandler.session_from_suid(suid)
|
||||
self.last_alive[csessid] = (time(), False)
|
||||
sess = self.sessionhandler.session_from_csessid(csessid)
|
||||
if sess:
|
||||
sess = sess[0]
|
||||
cmdarray = json.loads(request.args.get('data')[0])
|
||||
|
|
@ -216,18 +207,16 @@ class WebClient(resource.Resource):
|
|||
request (Request): Incoming request.
|
||||
|
||||
"""
|
||||
suid = request.args.get('suid', ['0'])[0]
|
||||
if suid == '0':
|
||||
return '""'
|
||||
self.last_alive[suid] = (time(), False)
|
||||
csessid = request.args.get('csessid')[0]
|
||||
self.last_alive[csessid] = (time(), False)
|
||||
|
||||
dataentries = self.databuffer.get(suid, [])
|
||||
dataentries = self.databuffer.get(csessid, [])
|
||||
if dataentries:
|
||||
return dataentries.pop(0)
|
||||
request.notifyFinish().addErrback(self._responseFailed, suid, request)
|
||||
if suid in self.requests:
|
||||
self.requests[suid].finish() # Clear any stale request.
|
||||
self.requests[suid] = request
|
||||
request.notifyFinish().addErrback(self._responseFailed, csessid, request)
|
||||
if csessid in self.requests:
|
||||
self.requests[csessid].finish() # Clear any stale request.
|
||||
self.requests[csessid] = request
|
||||
return server.NOT_DONE_YET
|
||||
|
||||
def mode_close(self, request):
|
||||
|
|
@ -239,16 +228,13 @@ class WebClient(resource.Resource):
|
|||
request (Request): Incoming request.
|
||||
|
||||
"""
|
||||
suid = request.args.get('suid', ['0'])[0]
|
||||
if suid == '0':
|
||||
self.client_disconnect(suid)
|
||||
else:
|
||||
try:
|
||||
sess = self.sessionhandler.session_from_suid(suid)[0]
|
||||
sess.sessionhandler.disconnect(sess)
|
||||
except IndexError:
|
||||
self.client_disconnect(suid)
|
||||
pass
|
||||
csessid = request.args.get('csessid')[0]
|
||||
try:
|
||||
sess = self.sessionhandler.session_from_csessid(csessid)[0]
|
||||
sess.sessionhandler.disconnect(sess)
|
||||
except IndexError:
|
||||
self.client_disconnect(csessid)
|
||||
pass
|
||||
return '""'
|
||||
|
||||
def render_POST(self, request):
|
||||
|
|
@ -306,8 +292,8 @@ class WebClientSession(session.Session):
|
|||
Args:
|
||||
reason (str): Motivation for the disconnect.
|
||||
"""
|
||||
self.client.lineSend(self.suid, ["connection_close", [reason], {}])
|
||||
self.client.client_disconnect(self.suid)
|
||||
self.client.lineSend(self.csessid, ["connection_close", [reason], {}])
|
||||
self.client.client_disconnect(self.csessid)
|
||||
self.sessionhandler.disconnect(self)
|
||||
|
||||
def data_out(self, **kwargs):
|
||||
|
|
@ -363,7 +349,7 @@ class WebClientSession(session.Session):
|
|||
args[0] = parse_html(text, strip_ansi=nomarkup)
|
||||
|
||||
# send to client on required form [cmdname, args, kwargs]
|
||||
self.client.lineSend(self.suid, [cmd, args, kwargs])
|
||||
self.client.lineSend(self.csessid, [cmd, args, kwargs])
|
||||
|
||||
def send_prompt(self, *args, **kwargs):
|
||||
kwargs["options"].update({"send_prompt": True})
|
||||
|
|
@ -385,4 +371,4 @@ class WebClientSession(session.Session):
|
|||
"""
|
||||
if not cmdname == "options":
|
||||
#print "ajax.send_default", cmdname, args, kwargs
|
||||
self.client.lineSend(self.suid, [cmdname, args, kwargs])
|
||||
self.client.lineSend(self.csessid, [cmdname, args, kwargs])
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from evennia.utils.utils import make_iter, lazy_property
|
|||
from evennia.commands.cmdsethandler import CmdSetHandler
|
||||
from evennia.server.session import Session
|
||||
|
||||
BrowserSessionStore = importlib.import_module(settings.SESSION_ENGINE).SessionStore
|
||||
ClientSessionStore = importlib.import_module(settings.SESSION_ENGINE).SessionStore
|
||||
|
||||
_GA = object.__getattribute__
|
||||
_SA = object.__setattr__
|
||||
|
|
@ -163,7 +163,6 @@ class ServerSession(Session):
|
|||
"Initiate to avoid AttributeErrors down the line"
|
||||
self.puppet = None
|
||||
self.player = None
|
||||
self.browserid = None
|
||||
self.cmdset_storage_string = ""
|
||||
self.cmdset = CmdSetHandler(self, True)
|
||||
|
||||
|
|
@ -224,12 +223,14 @@ class ServerSession(Session):
|
|||
self.puppet = None
|
||||
self.cmdset_storage = settings.CMDSET_SESSION
|
||||
|
||||
if self.browserid:
|
||||
# this is only set by a webclient inputcommand.
|
||||
bsession = BrowserSessionStore(session_key=self.browserid)
|
||||
bsession["logged_in"] = player.id # this also saves the bsession
|
||||
bsession.save()
|
||||
print ("serversession.login:", bsession.session_key)
|
||||
if self.csessid:
|
||||
# An existing client sessid is registered, thus a matching
|
||||
# Client Session must also exist. Update it so the website
|
||||
# can also see we are logged in.
|
||||
csession = ClientSessionStore(session_key=self.browserid)
|
||||
csession["logged_in"] = player.id
|
||||
csession.save()
|
||||
print ("serversession.login:", csession.session_key)
|
||||
|
||||
# Update account's last login time.
|
||||
self.player.last_login = timezone.now()
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class Session(object):
|
|||
"""
|
||||
|
||||
# names of attributes that should be affected by syncing.
|
||||
_attrs_to_sync = ('protocol_key', 'address', 'suid', 'sessid', 'uid',
|
||||
_attrs_to_sync = ('protocol_key', 'address', 'suid', 'sessid', 'uid', 'csessid',
|
||||
'uname', 'logged_in', 'puid',
|
||||
'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total',
|
||||
'protocol_flags', 'server_data', "cmdset_storage_string")
|
||||
|
|
@ -64,6 +64,8 @@ class Session(object):
|
|||
|
||||
# unique id for this session
|
||||
self.sessid = 0 # no sessid yet
|
||||
# client session id, if given by the client
|
||||
self.csessid = None
|
||||
# database id for the user connected to this session
|
||||
self.uid = None
|
||||
# user name, for easier tracking of sessions
|
||||
|
|
|
|||
|
|
@ -593,16 +593,17 @@ class ServerSessionHandler(SessionHandler):
|
|||
return sessions[0] if len(sessions) == 1 else sessions
|
||||
sessions_from_character = sessions_from_puppet
|
||||
|
||||
def sessions_from_browserid(self, browserid):
|
||||
def sessions_from_csessid(self, csessid):
|
||||
"""
|
||||
Given a browserid, return all sessions having this id.
|
||||
Given a cliend identification hash (for session types that offer them) return all sessions with
|
||||
a matching hash.
|
||||
|
||||
Args
|
||||
browserid (str): The browserid hash
|
||||
csessid (str): The session hash
|
||||
|
||||
"""
|
||||
return [session for session in self.values()
|
||||
if session.browserid and session.browserid == browserid]
|
||||
if session.csessid and session.csessid == csessid]
|
||||
|
||||
def announce_all(self, message):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue