First, untested implementation of websocket protocol support.
This commit is contained in:
parent
3b1c66dcbc
commit
01d15f13ee
6 changed files with 811 additions and 7 deletions
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
121
src/server/portal/websocket.py
Normal file
121
src/server/portal/websocket.py
Normal 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))
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue