From ef0a154a61caf8c8e36d4a59ab9eccb0797876c3 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 13 Apr 2014 16:26:14 +0200 Subject: [PATCH] First version of working websocket protocol. --- src/server/oob_msdp.py | 1 - src/server/oobhandler.py | 39 +++++++++++++++++++----------- src/server/portal/portal.py | 4 ++-- src/server/portal/websocket.py | 43 ++++++++++++++++++++-------------- src/settings_default.py | 2 +- 5 files changed, 53 insertions(+), 36 deletions(-) diff --git a/src/server/oob_msdp.py b/src/server/oob_msdp.py index d1be697b0c..9bafa9b79c 100644 --- a/src/server/oob_msdp.py +++ b/src/server/oob_msdp.py @@ -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 diff --git a/src/server/oobhandler.py b/src/server/oobhandler.py index 5b282895f4..d4f5eeda77 100644 --- a/src/server/oobhandler.py +++ b/src/server/oobhandler.py @@ -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() diff --git a/src/server/portal/portal.py b/src/server/portal/portal.py index a0a45f4068..c40bf5dedc 100644 --- a/src/server/portal/portal.py +++ b/src/server/portal/portal.py @@ -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) diff --git a/src/server/portal/websocket.py b/src/server/portal/websocket.py index bae77f8b2e..e457368db3 100644 --- a/src/server/portal/websocket.py +++ b/src/server/portal/websocket.py @@ -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 diff --git a/src/settings_default.py b/src/settings_default.py index e352d291fa..e84d88fc5d 100644 --- a/src/settings_default.py +++ b/src/settings_default.py @@ -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.