First version of working websocket protocol.
This commit is contained in:
parent
01d15f13ee
commit
ef0a154a61
5 changed files with 53 additions and 36 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue