First, untested implementation of websocket protocol support.

This commit is contained in:
Griatch 2014-04-13 13:18:32 +02:00
parent 3b1c66dcbc
commit 01d15f13ee
6 changed files with 811 additions and 7 deletions

View file

@ -239,4 +239,4 @@ class Msdp(object):
Send oob data to Evennia
"""
#print "msdp data_in:", funcname, args, kwargs
self.protocol.data_in(text=None, oob=(funcname, args, kwargs))
self.protocol.data_in(text=None, oob=(funcname, args, kwargs))

View file

@ -42,17 +42,20 @@ TELNET_PORTS = settings.TELNET_PORTS
SSL_PORTS = settings.SSL_PORTS
SSH_PORTS = settings.SSH_PORTS
WEBSERVER_PORTS = settings.WEBSERVER_PORTS
WEBSOCKET_PORTS = settings.WEBSOCKET_PORTS
TELNET_INTERFACES = settings.TELNET_INTERFACES
SSL_INTERFACES = settings.SSL_INTERFACES
SSH_INTERFACES = settings.SSH_INTERFACES
WEBSERVER_INTERFACES = settings.WEBSERVER_INTERFACES
WEBSOCKET_INTERFACES = settings.WEBSOCKET_INTERFACES
TELNET_ENABLED = settings.TELNET_ENABLED and TELNET_PORTS and TELNET_INTERFACES
SSL_ENABLED = settings.SSL_ENABLED and SSL_PORTS and SSL_INTERFACES
SSH_ENABLED = settings.SSH_ENABLED and SSH_PORTS and SSH_INTERFACES
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
WEBSOCKET_ENABLED = settings.WEBSOCKET_ENABLED and WEBSOCKET_PORTS and WEBSOCKET_INTERFACES
AMP_HOST = settings.AMP_HOST
AMP_PORT = settings.AMP_PORT
@ -165,6 +168,7 @@ if AMP_ENABLED:
amp_client.setName('evennia_amp')
PORTAL.services.addService(amp_client)
# We group all the various services under the same twisted app.
# These will gradually be started as they are initialized below.
@ -189,6 +193,7 @@ if TELNET_ENABLED:
print ' telnet%s: %s' % (ifacestr, port)
if SSL_ENABLED:
# Start SSL game connection (requires PyOpenSSL).
@ -236,6 +241,7 @@ if SSH_ENABLED:
print " ssl%s: %s" % (ifacestr, port)
if WEBSERVER_ENABLED:
# Start a reverse proxy to relay data to the Server-side webserver
@ -264,6 +270,30 @@ if WEBSERVER_ENABLED:
PORTAL.services.addService(proxy_service)
print " webproxy%s%s:%s (<-> %s)" % (webclientstr, ifacestr, proxyport, serverport)
if WEBSOCKET_ENABLED:
# websocket support is experimental!
# start websocket ports for real-time web communication
from src.server.portal import websocket
from src.utils.txws import WebSocketFactory
for interface in WEBSOCKET_INTERFACES:
ifacestr = ""
if interface not in ('0.0.0.0', '::') or len(WEBSOCKET_INTERFACES) > 1:
ifacestr = "-%s" % interface
for port in WEBSOCKET_PORTS:
pstring = "%s:%s" % (ifacestr, port)
factory = WebSocketFactory(protocol.ServerFactory())
factory.protocol = websocket.WebSocketProtocol
factory.sessionhandler = PORTAL_SESSIONS
websocket_service = internet.TCPServer(port, factory, interface=interface)
websocket_service.setName('EvenniaWebSocket%s' % pstring)
PORTAL.services.addService(websocket_service)
print ' websocket%s: %s' % (ifacestr, port)
for plugin_module in PORTAL_SERVICES_PLUGIN_MODULES:
# external plugin services to start
plugin_module.start_plugin_services(PORTAL)

View file

@ -84,12 +84,12 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def connectionLost(self, reason):
"""
This is executed when the connection is lost for
whatever reason. It can also be called directly, from
this is executed when the connection is lost for
whatever reason. it can also be called directly, from
the disconnect method
"""
self.sessionhandler.disconnect(self)
self.transport.loseConnection()
self.transport.loseconnection()
def dataReceived(self, data):
"""
@ -97,6 +97,11 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
starts with IAC (a telnet command) or not. All other data will
be handled in line mode. Some clients also sends an erroneous
line break after IAC, which we must watch out for.
OOB protocols (MSDP etc) already intercept subnegotiations
on their own, never entering this method. They will relay
their parsed data directly to self.data_in.
"""
if data and data[0] == IAC or self.iaw_mode:

View file

@ -0,0 +1,121 @@
"""
Websockets Protocol
This implements WebSockets (http://en.wikipedia.org/wiki/WebSocket)
by use of the txws implementation (https://github.com/MostAwesomeDude/txWS).
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), ...]
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).
"""
import json
from twisted.internet.protocol import Protocol
from src.server.session import Session
from src.utils.logger import log_trace
from src.utils.utils import to_str
from src.utils.text2html import parse_html
class WebSocketProtocol(Protocol, Session):
"""
This is called when the connection is first established
"""
def connectionMade(self):
"""
This is called when the connection is first established.
"""
client_address = self.transport.client
self.init_session("websocket", client_address, self.factory.sessionhandler)
self.sessionhandler.connect(self)
def disconnect(self, reason=None):
"""
generic hook for the engine to call in order to
disconnect this protocol.
"""
if reason:
self.data_out(text=reason)
self.connectionLost(reason)
def connectionLost(self, reason):
"""
this is executed when the connection is lost for
whatever reason. it can also be called directly, from
the disconnect method
"""
self.sessionhandler.disconnect(self)
self.transport.loseconnection()
def dataReceived(self, string):
"""
Method called when data is coming in over
the websocket connection.
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.
any other prefix (or lack of prefix) is considered
plain text data, to be treated like a game
input command.
"""
if string[:3] == "OOB":
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)
else:
# plain text input
self.data_in(text=string)
def data_in(self, text=None, **kwargs):
"""
Data Websocket -> Server
"""
self.sessionhandler.data_in(self, text=text, **kwargs)
def data_out(self, text=None, **kwargs):
"""
Data Evennia -> Player.
generic hook method for engine to call in order to send data
through the websocket connection.
valid webclient kwargs:
oob=<string> - supply an Out-of-Band instruction.
raw=True - no parsing at all (leave ansi-to-html markers unparsed)
nomarkup=True - clean out all ansi/html markers and tokens
"""
try:
text = to_str(text if text else "", encoding=self.encoding)
except Exception, e:
self.sendLine(str(e))
if "oob" in kwargs:
oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob"))
self.sendLine("OOB" + json.dumps(oobstruct))
raw = kwargs.get("raw", False)
nomarkup = kwargs.get("nomarkup", False)
if raw:
self.sendLine(text)
else:
self.sendLine(parse_html(text, strip_ansi=nomarkup))