First version of working websocket protocol.

This commit is contained in:
Griatch 2014-04-13 16:26:14 +02:00
parent 01d15f13ee
commit ef0a154a61
5 changed files with 53 additions and 36 deletions

View file

@ -210,7 +210,6 @@ def oob_error(oobhandler, session, errmsg, *args, **kwargs):
""" """
session.msg(oob=("send", {"ERROR": errmsg})) session.msg(oob=("send", {"ERROR": errmsg}))
def list(oobhandler, session, mode, *args, **kwargs): def list(oobhandler, session, mode, *args, **kwargs):
""" """
List available properties. Mode is the type of information List available properties. Mode is the type of information

View file

@ -1,31 +1,36 @@
""" """
OOBHandler - Out Of Band Handler OOBHandler - Out Of Band Handler
The OOBHandler is called directly by out-of-band protocols. It supplies three The OOBHandler.execute_cmd is called by the sessionhandler when it detects
pieces of functionality: an OOB instruction (exactly how this looked depends on the protocol; at this
point all oob calls should look the same)
The handler pieces of functionality:
function execution - the oob protocol can execute a function directly on function execution - the oob protocol can execute a function directly on
the server. The available functions must be defined the server. The available functions must be defined
as global functions via settings.OOB_PLUGIN_MODULES. as global functions in settings.OOB_PLUGIN_MODULES.
repeat func execution - the oob protocol can request a given function be repeat func execution - the oob protocol can request a given function be
executed repeatedly at a regular interval. This executed repeatedly at a regular interval. This
uses an internal script pool. uses an internal script pool.
tracking - the oob protocol can request Evennia to track changes to tracking - the oob protocol can request Evennia to track changes to
fields on objects, as well as changes in Attributes. This is fields on objects, as well as changes in Attributes. This is
done by dynamically adding tracker-objects on entities. The done by dynamically adding tracker-objects on entities. The
behaviour of those objects can be customized via behaviour of those objects can be customized by adding new
settings.OOB_PLUGIN_MODULES. tracker classes in settings.OOB_PLUGIN_MODULES.
What goes into the OOB_PLUGIN_MODULES is a list of modules with input What goes into the OOB_PLUGIN_MODULES is a (list of) modules that contains
for the OOB system. the working server-side code available to the OOB system: oob functions and
tracker classes.
oob functions have the following call signature: oob functions have the following call signature:
function(caller, *args, **kwargs) function(caller, session, *args, **kwargs)
oob trackers should inherit from the OOBTracker class in this oob trackers should inherit from the OOBTracker class (in this
module and implement a minimum of the same functionality. module) and implement a minimum of the same functionality.
a global function oob_error will be used as optional error management. If a function named "oob_error" is given, this will be called with error
messages.
""" """
@ -46,12 +51,18 @@ _SA = object.__setattr__
_GA = object.__getattribute__ _GA = object.__getattribute__
_DA = object.__delattr__ _DA = object.__delattr__
# load from plugin module # load resources from plugin module
_OOB_FUNCS = {} _OOB_FUNCS = {}
for mod in make_iter(settings.OOB_PLUGIN_MODULES): for mod in make_iter(settings.OOB_PLUGIN_MODULES):
_OOB_FUNCS.update(dict((key.lower(), func) for key, func in all_from_module(mod).items() if isfunction(func))) _OOB_FUNCS.update(dict((key.lower(), func) for key, func in all_from_module(mod).items() if isfunction(func)))
# get custom error method or use the default
_OOB_ERROR = _OOB_FUNCS.get("oob_error", None) _OOB_ERROR = _OOB_FUNCS.get("oob_error", None)
if not _OOB_ERROR:
# create default oob error message function
def oob_error(oobhandler, session, errmsg, *args, **kwargs):
session.msg(oob=("send", {"ERROR": errmsg}))
_OOB_ERROR = oob_error
class TrackerHandler(object): class TrackerHandler(object):
""" """
@ -444,13 +455,13 @@ class OOBHandler(object):
_OOB_ERROR(self, session, errmsg, *args, **kwargs) _OOB_ERROR(self, session, errmsg, *args, **kwargs)
else: else:
logger.log_trace(errmsg) logger.log_trace(errmsg)
raise raise KeyError(errmsg)
except Exception, err: except Exception, err:
errmsg = "OOB Error: Exception in '%s'(%s, %s):\n%s" % (func_key, args, kwargs, err) errmsg = "OOB Error: Exception in '%s'(%s, %s):\n%s" % (func_key, args, kwargs, err)
if _OOB_ERROR: if _OOB_ERROR:
_OOB_ERROR(self, session, errmsg, *args, **kwargs) _OOB_ERROR(self, session, errmsg, *args, **kwargs)
else: else:
logger.log_trace(errmsg) logger.log_trace(errmsg)
raise raise Exception(errmsg)
# access object # access object
OOB_HANDLER = OOBHandler() OOB_HANDLER = OOBHandler()

View file

@ -285,10 +285,10 @@ if WEBSOCKET_ENABLED:
ifacestr = "-%s" % interface ifacestr = "-%s" % interface
for port in WEBSOCKET_PORTS: for port in WEBSOCKET_PORTS:
pstring = "%s:%s" % (ifacestr, port) pstring = "%s:%s" % (ifacestr, port)
factory = WebSocketFactory(protocol.ServerFactory()) factory = protocol.ServerFactory()
factory.protocol = websocket.WebSocketProtocol factory.protocol = websocket.WebSocketProtocol
factory.sessionhandler = PORTAL_SESSIONS factory.sessionhandler = PORTAL_SESSIONS
websocket_service = internet.TCPServer(port, factory, interface=interface) websocket_service = internet.TCPServer(port, WebSocketFactory(factory), interface=interface)
websocket_service.setName('EvenniaWebSocket%s' % pstring) websocket_service.setName('EvenniaWebSocket%s' % pstring)
PORTAL.services.addService(websocket_service) PORTAL.services.addService(websocket_service)

View file

@ -9,13 +9,22 @@ Thanks to Ricard Pillosu whose Evennia plugin inspired this module.
Communication over the websocket interface is done with normal text Communication over the websocket interface is done with normal text
communication. A special case is OOB-style communication; to do this communication. A special case is OOB-style communication; to do this
the client must send data on the following form: the client must send data on the following form:
OOB(oobfunc, args, kwargs)
or OOB{oobfunc:[[args], {kwargs}], ...}
OOB[(oobfunc, args, kwargs), ...]
where the tuple/list is sent json-encoded. The initial OOB-prefix where the tuple/list is sent json-encoded. The initial OOB-prefix
is used to identify this type of communication, all other data is used to identify this type of communication, all other data
is considered plain text (command input). is considered plain text (command input).
Example of call from javascript client:
websocket = new WeSocket("ws://localhost:8021")
var msg1 = "WebSocket Test"
websocket.send(msg1)
var msg2 = JSON.stringify({"testfunc":[[1,2,3], {"kwarg":"val"}]})
websocket.send("OOB" + msg2)
websocket.close()
""" """
import json import json
from twisted.internet.protocol import Protocol from twisted.internet.protocol import Protocol
@ -52,7 +61,7 @@ class WebSocketProtocol(Protocol, Session):
the disconnect method the disconnect method
""" """
self.sessionhandler.disconnect(self) self.sessionhandler.disconnect(self)
self.transport.loseconnection() self.transport.close()
def dataReceived(self, string): def dataReceived(self, string):
""" """
@ -62,11 +71,8 @@ class WebSocketProtocol(Protocol, Session):
Type of data is identified by a 3-character Type of data is identified by a 3-character
prefix. prefix.
OOB - This is an Out-of-band instruction. If so, OOB - This is an Out-of-band instruction. If so,
the remaining string should either be the remaining string should be a json-packed
a json packed tuple (oobfuncname, args, kwargs) string on the form {oobfuncname: [[args], {kwargs}], ...}
or a json-packed list of tuples
[(oobfuncname, args, kwargs), ...] to send to
the OOBhandler.
any other prefix (or lack of prefix) is considered any other prefix (or lack of prefix) is considered
plain text data, to be treated like a game plain text data, to be treated like a game
input command. input command.
@ -75,19 +81,20 @@ class WebSocketProtocol(Protocol, Session):
string = string[3:] string = string[3:]
try: try:
oobdata = json.loads(string) oobdata = json.loads(string)
if isinstance(oobdata, list): for (key, argstuple) in oobdata.items():
for oobtuple in oobdata: args = argstuple[0] if argstuple else []
self.data_in(oob=oobtuple) kwargs = argstuple[1] if len(argstuple) > 1 else {}
elif isinstance(oobdata, tuple): self.data_in(oob=(key, args, kwargs))
self.data_in(oob=oobtuple) except Exception:
else: log_trace("Websocket malformed OOB request: %s" % string)
raise RuntimeError("OOB data is not list or tuple.")
except:
log_trace("Websocket malformed OOB request: %s" % oobdata)
else: else:
# plain text input # plain text input
self.data_in(text=string) self.data_in(text=string)
def sendLine(self, line):
"send data to client"
return self.transport.write(line)
def data_in(self, text=None, **kwargs): def data_in(self, text=None, **kwargs):
""" """
Data Websocket -> Server Data Websocket -> Server

View file

@ -78,7 +78,7 @@ WEBSOCKET_ENABLED = False
# Ports to use for Websockets # Ports to use for Websockets
WEBSOCKET_PORTS = [8021] WEBSOCKET_PORTS = [8021]
# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6. # Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6.
WEBSOCKET_INTERFACES = ['0.0.0.0.'] WEBSOCKET_INTERFACES = ['0.0.0.0']
# The path that contains this settings.py file (no trailing slash). # The path that contains this settings.py file (no trailing slash).
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Path to the src directory containing the bulk of the codebase's code. # Path to the src directory containing the bulk of the codebase's code.