mirror of
https://github.com/evennia/evennia.git
synced 2026-04-05 23:47:16 +02:00
Implemented an AJAX keepalive from the Evennia side to make sure a dead AJAX connection (commonly if the user closes the window/browser without properly logging out), will eventually time out and log off the Player. Resolves #951.
This commit is contained in:
parent
795061907f
commit
c3bb30c46f
2 changed files with 77 additions and 18 deletions
|
|
@ -16,13 +16,12 @@ http://localhost:8000/webclient.)
|
|||
handle these requests and act as a gateway
|
||||
to sessions connected over the webclient.
|
||||
"""
|
||||
import time
|
||||
import json
|
||||
|
||||
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
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.conf import settings
|
||||
|
|
@ -30,8 +29,8 @@ from evennia.utils import utils
|
|||
from evennia.utils.text2html import parse_html
|
||||
from evennia.server import session
|
||||
|
||||
SERVERNAME = settings.SERVERNAME
|
||||
ENCODINGS = settings.ENCODINGS
|
||||
_SERVERNAME = settings.SERVERNAME
|
||||
_KEEPALIVE = 30 # how often to check keepalive
|
||||
|
||||
# defining a simple json encoder for returning
|
||||
# django data to the client. Might need to
|
||||
|
|
@ -66,11 +65,8 @@ class WebClient(resource.Resource):
|
|||
self.requests = {}
|
||||
self.databuffer = {}
|
||||
|
||||
#def getChild(self, path, request):
|
||||
# """
|
||||
# This is the place to put dynamic content.
|
||||
# """
|
||||
# return self
|
||||
self.last_alive = {}
|
||||
self.keep_alive = None
|
||||
|
||||
def _responseFailed(self, failure, suid, request):
|
||||
"callback if a request is lost/timed out"
|
||||
|
|
@ -79,6 +75,33 @@ class WebClient(resource.Resource):
|
|||
except KeyError:
|
||||
pass
|
||||
|
||||
def _keepalive(self):
|
||||
"""
|
||||
Callback for checking the connection is still alive.
|
||||
"""
|
||||
now = time()
|
||||
to_remove = []
|
||||
keep_alives = ((suid, remove) for suid, (t, remove)
|
||||
in self.last_alive.iteritems() if now - t > _KEEPALIVE)
|
||||
for suid, remove in keep_alives:
|
||||
if remove:
|
||||
# keepalive timeout. Line is dead.
|
||||
to_remove.append(suid)
|
||||
else:
|
||||
# normal timeout - send keepalive
|
||||
self.last_alive[suid] = (now, True)
|
||||
self.lineSend(suid, ["ajax_keepalive", [], {}])
|
||||
# remove timed-out sessions
|
||||
for suid in to_remove:
|
||||
sess = self.sessionhandler.session_from_suid(suid)
|
||||
if sess:
|
||||
sess[0].disconnect()
|
||||
self.last_alive.pop(suid, 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):
|
||||
"""
|
||||
This adds the data to the buffer and/or sends it to the client
|
||||
|
|
@ -127,10 +150,10 @@ class WebClient(resource.Resource):
|
|||
suid = request.args.get('suid', ['0'])[0]
|
||||
|
||||
remote_addr = request.getClientIP()
|
||||
host_string = "%s (%s:%s)" % (SERVERNAME, request.getRequestHostname(), request.getHost().port)
|
||||
host_string = "%s (%s:%s)" % (_SERVERNAME, request.getRequestHostname(), request.getHost().port)
|
||||
if suid == '0':
|
||||
# creating a unique id hash string
|
||||
suid = md5(str(time.time())).hexdigest()
|
||||
suid = md5(str(time())).hexdigest()
|
||||
self.databuffer[suid] = []
|
||||
|
||||
sess = WebClientSession()
|
||||
|
|
@ -138,8 +161,27 @@ class WebClient(resource.Resource):
|
|||
sess.init_session("webclient", remote_addr, self.sessionhandler)
|
||||
sess.suid = suid
|
||||
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)
|
||||
|
||||
return jsonify({'msg': host_string, 'suid': suid})
|
||||
|
||||
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 '""'
|
||||
print "keepalive succeeded"
|
||||
self.last_alive[suid] = (time(), False)
|
||||
return '""'
|
||||
|
||||
def mode_input(self, request):
|
||||
"""
|
||||
This is called by render_POST when the client
|
||||
|
|
@ -152,6 +194,8 @@ class WebClient(resource.Resource):
|
|||
suid = request.args.get('suid', ['0'])[0]
|
||||
if suid == '0':
|
||||
return '""'
|
||||
|
||||
self.last_alive[suid] = (time(), False)
|
||||
sess = self.sessionhandler.session_from_suid(suid)
|
||||
if sess:
|
||||
sess = sess[0]
|
||||
|
|
@ -173,6 +217,7 @@ class WebClient(resource.Resource):
|
|||
suid = request.args.get('suid', ['0'])[0]
|
||||
if suid == '0':
|
||||
return '""'
|
||||
self.last_alive[suid] = (time(), False)
|
||||
|
||||
dataentries = self.databuffer.get(suid, [])
|
||||
if dataentries:
|
||||
|
|
@ -230,8 +275,11 @@ class WebClient(resource.Resource):
|
|||
elif dmode == 'close':
|
||||
# the client is closing
|
||||
return self.mode_close(request)
|
||||
elif dmode == 'keepalive':
|
||||
# A reply to our keepalive request - all is well
|
||||
return self.mode_keepalive(request)
|
||||
else:
|
||||
# this should not happen if client sends valid data.
|
||||
# This should not happen if client sends valid data.
|
||||
return '""'
|
||||
|
||||
|
||||
|
|
@ -245,6 +293,10 @@ class WebClientSession(session.Session):
|
|||
This represents a session running in a webclient.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.protocol_name = "ajax/comet"
|
||||
super(WebClientSession, self).__init__(*args, **kwargs)
|
||||
|
||||
def disconnect(self, reason="Server disconnected."):
|
||||
"""
|
||||
Disconnect from server.
|
||||
|
|
@ -254,6 +306,7 @@ class WebClientSession(session.Session):
|
|||
"""
|
||||
self.client.lineSend(self.suid, ["connection_close", [reason], {}])
|
||||
self.client.client_disconnect(self.suid)
|
||||
self.sessionhandler.disconnect(self)
|
||||
|
||||
def data_out(self, **kwargs):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -195,7 +195,6 @@ An "emitter" object must have a function
|
|||
//
|
||||
var WebsocketConnection = function () {
|
||||
log("Trying websocket ...");
|
||||
wsurl = "ws://blah";
|
||||
var open = false;
|
||||
var websocket = new WebSocket(wsurl);
|
||||
// Handle Websocket open event
|
||||
|
|
@ -275,11 +274,12 @@ An "emitter" object must have a function
|
|||
};
|
||||
|
||||
// Send Client -> Evennia. Called by Evennia.msg
|
||||
var msg = function(data) {
|
||||
var msg = function(data, inmode) {
|
||||
$.ajax({type: "POST", url: "/webclientdata",
|
||||
async: true, cache: false, timeout: 30000,
|
||||
dataType: "json",
|
||||
data: {mode:'input', data: JSON.stringify(data), 'suid': client_hash},
|
||||
data: {mode: inmode == null ? 'input' : inmode,
|
||||
data: JSON.stringify(data), 'suid': client_hash},
|
||||
success: function(req, stat, err) {},
|
||||
error: function(req, stat, err) {
|
||||
Evennia.emit("connection_error", ["AJAX/COMET send error"], err);
|
||||
|
|
@ -298,8 +298,14 @@ An "emitter" object must have a function
|
|||
dataType: "json",
|
||||
data: {mode: 'receive', 'suid': client_hash},
|
||||
success: function(data) {
|
||||
log("ajax data received:", data);
|
||||
Evennia.emit(data[0], data[1], data[2]);
|
||||
// log("ajax data received:", data);
|
||||
if (data[0] === "ajax_keepalive") {
|
||||
// special ajax keepalive check - return immediately
|
||||
msg("", "keepalive");
|
||||
} else {
|
||||
// not a keepalive
|
||||
Evennia.emit(data[0], data[1], data[2]);
|
||||
}
|
||||
poll(); // immiately start a new request
|
||||
},
|
||||
error: function(req, stat, err) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue