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}))
def list(oobhandler, session, mode, *args, **kwargs):
"""
List available properties. Mode is the type of information

View file

@ -1,31 +1,36 @@
"""
OOBHandler - Out Of Band Handler
The OOBHandler is called directly by out-of-band protocols. It supplies three
pieces of functionality:
The OOBHandler.execute_cmd is called by the sessionhandler when it detects
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
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
executed repeatedly at a regular interval. This
uses an internal script pool.
tracking - the oob protocol can request Evennia to track changes to
fields on objects, as well as changes in Attributes. This is
done by dynamically adding tracker-objects on entities. The
behaviour of those objects can be customized via
settings.OOB_PLUGIN_MODULES.
behaviour of those objects can be customized by adding new
tracker classes in settings.OOB_PLUGIN_MODULES.
What goes into the OOB_PLUGIN_MODULES is a list of modules with input
for the OOB system.
What goes into the OOB_PLUGIN_MODULES is a (list of) modules that contains
the working server-side code available to the OOB system: oob functions and
tracker classes.
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
module and implement a minimum of the same functionality.
oob trackers should inherit from the OOBTracker class (in this
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__
_DA = object.__delattr__
# load from plugin module
# load resources from plugin module
_OOB_FUNCS = {}
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)))
# get custom error method or use the default
_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):
"""
@ -444,13 +455,13 @@ class OOBHandler(object):
_OOB_ERROR(self, session, errmsg, *args, **kwargs)
else:
logger.log_trace(errmsg)
raise
raise KeyError(errmsg)
except Exception, err:
errmsg = "OOB Error: Exception in '%s'(%s, %s):\n%s" % (func_key, args, kwargs, err)
if _OOB_ERROR:
_OOB_ERROR(self, session, errmsg, *args, **kwargs)
else:
logger.log_trace(errmsg)
raise
raise Exception(errmsg)
# access object
OOB_HANDLER = OOBHandler()

View file

@ -285,10 +285,10 @@ if WEBSOCKET_ENABLED:
ifacestr = "-%s" % interface
for port in WEBSOCKET_PORTS:
pstring = "%s:%s" % (ifacestr, port)
factory = WebSocketFactory(protocol.ServerFactory())
factory = protocol.ServerFactory()
factory.protocol = websocket.WebSocketProtocol
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)
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. A special case is OOB-style communication; to do this
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
is used to identify this type of communication, all other data
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
from twisted.internet.protocol import Protocol
@ -52,7 +61,7 @@ class WebSocketProtocol(Protocol, Session):
the disconnect method
"""
self.sessionhandler.disconnect(self)
self.transport.loseconnection()
self.transport.close()
def dataReceived(self, string):
"""
@ -62,11 +71,8 @@ class WebSocketProtocol(Protocol, Session):
Type of data is identified by a 3-character
prefix.
OOB - This is an Out-of-band instruction. If so,
the remaining string should either be
a json packed tuple (oobfuncname, args, kwargs)
or a json-packed list of tuples
[(oobfuncname, args, kwargs), ...] to send to
the OOBhandler.
the remaining string should be a json-packed
string on the form {oobfuncname: [[args], {kwargs}], ...}
any other prefix (or lack of prefix) is considered
plain text data, to be treated like a game
input command.
@ -75,19 +81,20 @@ class WebSocketProtocol(Protocol, Session):
string = string[3:]
try:
oobdata = json.loads(string)
if isinstance(oobdata, list):
for oobtuple in oobdata:
self.data_in(oob=oobtuple)
elif isinstance(oobdata, tuple):
self.data_in(oob=oobtuple)
else:
raise RuntimeError("OOB data is not list or tuple.")
except:
log_trace("Websocket malformed OOB request: %s" % oobdata)
for (key, argstuple) in oobdata.items():
args = argstuple[0] if argstuple else []
kwargs = argstuple[1] if len(argstuple) > 1 else {}
self.data_in(oob=(key, args, kwargs))
except Exception:
log_trace("Websocket malformed OOB request: %s" % string)
else:
# plain text input
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):
"""
Data Websocket -> Server

View file

@ -78,7 +78,7 @@ WEBSOCKET_ENABLED = False
# Ports to use for Websockets
WEBSOCKET_PORTS = [8021]
# 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).
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.