Switch to autobahn-python for WebSockets support.

This commit is contained in:
Ryan Stein 2017-10-29 21:29:09 -04:00
parent b80fb95662
commit bb15fed784
5 changed files with 42 additions and 714 deletions

View file

@ -295,25 +295,25 @@ if WEBSERVER_ENABLED:
ajax_webclient = webclient_ajax.AjaxWebClient()
ajax_webclient.sessionhandler = PORTAL_SESSIONS
web_root.putChild("webclientdata", ajax_webclient)
web_root.putChild(b"webclientdata", ajax_webclient)
webclientstr = "\n + webclient (ajax only)"
if WEBSOCKET_CLIENT_ENABLED and not websocket_started:
# start websocket client port for the webclient
# we only support one websocket client
from evennia.server.portal import webclient
from evennia.utils.txws import WebSocketFactory
from autobahn.twisted.websocket import WebSocketServerFactory
w_interface = WEBSOCKET_CLIENT_INTERFACE
w_ifacestr = ''
if w_interface not in ('0.0.0.0', '::') or len(WEBSERVER_INTERFACES) > 1:
w_ifacestr = "-%s" % interface
port = WEBSOCKET_CLIENT_PORT
factory = protocol.ServerFactory()
factory = WebSocketServerFactory()
factory.noisy = False
factory.protocol = webclient.WebSocketClient
factory.sessionhandler = PORTAL_SESSIONS
websocket_service = internet.TCPServer(port, WebSocketFactory(factory), interface=w_interface)
websocket_service = internet.TCPServer(port, factory, interface=w_interface)
websocket_service.setName('EvenniaWebSocket%s:%s' % (w_ifacestr, proxyport))
PORTAL.services.addService(websocket_service)
websocket_started = True

View file

@ -2,8 +2,8 @@
Webclient based on websockets.
This implements a webclient with WebSockets (http://en.wikipedia.org/wiki/WebSocket)
by use of the txws implementation (https://github.com/MostAwesomeDude/txWS). It is
used together with evennia/web/media/javascript/evennia_websocket_webclient.js.
by use of the autobahn-python package's implementation (https://github.com/crossbario/autobahn-python).
It is used together with evennia/web/media/javascript/evennia_websocket_webclient.js.
All data coming into the webclient is in the form of valid JSON on the form
@ -22,26 +22,17 @@ from evennia.server.session import Session
from evennia.utils.utils import to_str, mod_import
from evennia.utils.ansi import parse_ansi
from evennia.utils.text2html import parse_html
from autobahn.twisted.websocket import WebSocketServerProtocol
_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):
class WebSocketClient(WebSocketServerProtocol, Session):
"""
Implements the server-side of the Websocket connection.
"""
def connectionMade(self):
"""
This is called when the connection is first established.
"""
self.transport.validationMade = self.validationMade
client_address = self.transport.client
client_address = client_address[0] if client_address else None
self.init_session("websocket", client_address, self.factory.sessionhandler)
def get_client_session(self):
"""
Get the Client browser session (used for auto-login based on browser session)
@ -52,7 +43,7 @@ class WebSocketClient(Protocol, Session):
"""
try:
self.csessid = self.transport.location.split("?", 1)[1]
self.csessid = self.http_request_uri.split("?", 1)[1]
except IndexError:
# this may happen for custom webclients not caring for the
# browser session.
@ -61,12 +52,15 @@ class WebSocketClient(Protocol, Session):
if self.csessid:
return _CLIENT_SESSIONS(session_key=self.csessid)
def validationMade(self):
def onOpen(self):
"""
This is called from the (modified) txws websocket library when
the ws handshake and validation has completed fully.
This is called when the WebSocket connection is fully established.
"""
client_address = self.transport.client
client_address = client_address[0] if client_address else None
self.init_session("websocket", client_address, self.factory.sessionhandler)
csession = self.get_client_session()
uid = csession and csession.get("webclient_authenticated_uid", None)
if uid:
@ -85,43 +79,48 @@ class WebSocketClient(Protocol, Session):
disconnect this protocol.
Args:
reason (str): Motivation for the disconnection.
reason (str or None): Motivation for the disconnection.
"""
self.data_out(text=((reason or "",), {}))
# autobahn-python: 1000 for a normal close, 3000-4999 for app. specific,
# in case anyone wants to expose this functionality later.
#
# sendClose() under autobahn/websocket/interfaces.py
self.sendClose(1000, reason)
csession = self.get_client_session()
if csession:
csession["webclient_authenticated_uid"] = None
csession.save()
self.logged_in = False
self.connectionLost(reason)
def connectionLost(self, reason):
def onClose(self, wasClean, code=None, reason=None):
"""
This is executed when the connection is lost for whatever
reason. it can also be called directly, from the disconnect
method.
Args:
wasClean (bool): ``True`` if the WebSocket was closed cleanly.
reason (str): Motivation for the lost connection.
code (int or None): Close status as sent by the WebSocket peer.
reason (str or None): Close reason as sent by the WebSocket peer.
"""
print("In connectionLost of webclient")
csession = self.get_client_session()
if csession:
csession["webclient_authenticated_uid"] = None
csession.save()
self.logged_in = False
self.sessionhandler.disconnect(self)
self.transport.close()
def dataReceived(self, string):
def onMessage(self, payload, isBinary):
"""
Method called when data is coming in over the websocket
connection. This is always a JSON object on the following
form:
[cmdname, [args], {kwargs}]
Callback fired when a complete WebSocket message was received.
Args:
payload (bytes): The WebSocket message received.
isBinary (bool): Flag indicating whether payload is binary or
UTF-8 encoded text.
"""
cmdarray = json.loads(string)
cmdarray = json.loads(payload)
if cmdarray:
self.data_in(**{cmdarray[0]: [cmdarray[1], cmdarray[2]]})
@ -133,7 +132,7 @@ class WebSocketClient(Protocol, Session):
line (str): Text to send.
"""
return self.transport.write(line)
return self.sendMessage(line.encode())
def at_login(self):
csession = self.get_client_session()
@ -162,22 +161,12 @@ class WebSocketClient(Protocol, Session):
this point.
"""
if "websocket_close" in kwargs:
self.disconnect()
return
self.sessionhandler.data_in(self, **kwargs)
def data_out(self, **kwargs):
"""
Data Evennia->User.
Kwargs:
kwargs (any): Options ot the protocol
"""
self.sessionhandler.data_out(self, **kwargs)
def send_text(self, *args, **kwargs):
"""
Send text data. This will pre-process the text for