Updated to a working websocket implementation of webclient.

This commit is contained in:
Griatch 2014-06-26 23:37:03 +02:00
parent ca1e36da5f
commit d59500f574
6 changed files with 133 additions and 121 deletions

View file

@ -42,20 +42,21 @@ 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
WEBSOCKET_CLIENT_PORT = settings.WEBSOCKET_CLIENT_PORT
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
WEBSOCKET_CLIENT_INTERFACE = settings.WEBSOCKET_CLIENT_INTERFACE
WEBSOCKET_CLIENT_URL = settings.WEBSOCKET_CLIENT_URL
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
WEBSOCKET_CLIENT_ENABLED = settings.WEBSOCKET_CLIENT_ENABLED and WEBSOCKET_CLIENT_PORT and WEBSOCKET_CLIENT_INTERFACE
AMP_HOST = settings.AMP_HOST
AMP_PORT = settings.AMP_PORT
@ -257,16 +258,31 @@ if WEBSERVER_ENABLED:
if WEBCLIENT_ENABLED:
# create ajax client processes at /webclientdata
from src.server.portal.webclient import WebClient
webclient = WebClient()
webclient.sessionhandler = PORTAL_SESSIONS
web_root.putChild("webclientdata", webclient)
webclientstr = "/client"
webclientstr = "\n + client (ajax only)"
#from src.server.portal import websocket
#factory = protocol.ServerFactory()
#websocketclient = websocket.WebSocketProtocol()
#websocketclient.sessionhandler = PORTAL_SESSIONS
#web_root.putChild("websocket", websocketclient)
if WEBSOCKET_CLIENT_ENABLED:
# start websocket client port for the webclient
from src.server.portal import websocket_client
from src.utils.txws import WebSocketFactory
interface = WEBSOCKET_CLIENT_INTERFACE
port = WEBSOCKET_CLIENT_PORT
ifacestr = ""
if interface not in ('0.0.0.0', '::'):
ifacestr = "-%s" % interface
pstring = "%s:%s" % (ifacestr, port)
factory = protocol.ServerFactory()
factory.protocol = websocket_client.WebSocketClient
factory.sessionhandler = PORTAL_SESSIONS
websocket_service = internet.TCPServer(port, WebSocketFactory(factory), interface=interface)
websocket_service.setName('EvenniaWebSocket%s' % pstring)
PORTAL.services.addService(websocket_service)
webclientstr = webclientstr[:-11] + "(%s:%s)" % (WEBSOCKET_CLIENT_URL, port)
web_root = server.Site(web_root, logPath=settings.HTTP_LOG_FILE)
proxy_service = internet.TCPServer(proxyport,
@ -274,30 +290,8 @@ if WEBSERVER_ENABLED:
interface=interface)
proxy_service.setName('EvenniaWebProxy%s' % pstring)
PORTAL.services.addService(proxy_service)
print " webproxy%s%s:%s (<-> %s)" % (webclientstr, ifacestr, proxyport, serverport)
print " webproxy%s:%s (<-> %s)%s" % (ifacestr, proxyport, serverport, webclientstr)
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 = protocol.ServerFactory()
factory.protocol = websocket.WebSocketProtocol
factory.sessionhandler = PORTAL_SESSIONS
websocket_service = internet.TCPServer(port, WebSocketFactory(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

View file

@ -1,8 +1,9 @@
"""
Websockets Protocol
Websocket-webclient
This implements WebSockets (http://en.wikipedia.org/wiki/WebSocket)
by use of the txws implementation (https://github.com/MostAwesomeDude/txWS).
This implements a webclient with WebSockets (http://en.wikipedia.org/wiki/WebSocket)
by use of the txws implementation (https://github.com/MostAwesomeDude/txWS). It is
used together with src/web/media/javascript/evennia_websocket_webclient.js.
Thanks to Ricard Pillosu whose Evennia plugin inspired this module.
@ -10,13 +11,13 @@ 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}], ...}
OOB{"func1":[args], "func2":[args], ...}
where the tuple/list is sent json-encoded. The initial OOB-prefix
where the dict is 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:
Example of call from a javascript client:
websocket = new WeSocket("ws://localhost:8021")
var msg1 = "WebSocket Test"
@ -33,9 +34,10 @@ 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):
class WebSocketClient(Protocol, Session):
"""
This is called when the connection is first established
Implements the server-side of the Websocket connection.
"""
def connectionMade(self):

View file

@ -58,18 +58,22 @@ UPSTREAM_IPS = ['127.0.0.1']
# with server load. Set the minimum and maximum number of threads it
# may use as (min, max) (must be > 0)
WEBSERVER_THREADPOOL_LIMITS = (1, 20)
# Start the evennia ajax client on /webclient
# (the webserver must also be running)
# Start the evennia webclient. This requires the webserver to be running and
# offers the fallback ajax-based webclient backbone for browsers not supporting
# the websocket one.
WEBCLIENT_ENABLED = True
# Activate Websocket support. If this is on, the default webclient will use this
# before going for the ajax implementation
WEBSOCKET_ENABLED = True
# Ports to use for Websockets. If this is changed, you must also update
# src/web/media/javascript/evennia_websocket_webclient.js to match.
WEBSOCKET_PORTS = [8001]
# Activate Websocket support for modern browsers. If this is on, the
# default webclient will use this and only use the ajax version of the browser
# is too old to support websockets. Requires WEBCLIENT_ENABLED.
WEBSOCKET_CLIENT_ENABLED = True
# Server-side websocket port to open for the webclient.
WEBSOCKET_CLIENT_PORT = 8001
# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6.
WEBSOCKET_INTERFACES = ['0.0.0.0']
# Activate SSH protocol (SecureShell)
WEBSOCKET_CLIENT_INTERFACE = '0.0.0.0'
# Actual URL for webclient component to reach the websocket. The first
# port number in the WEBSOCKET_PORTS list will be automatically appended.
WEBSOCKET_CLIENT_URL = "ws://localhost"
# Activate SSH protocol communication (SecureShell)
SSH_ENABLED = False
# Ports to use for SSH
SSH_PORTS = [8022]

View file

@ -2,30 +2,39 @@
Evennia websocket webclient (javascript component)
The client is composed of several parts:
templates/webclient.html - the main page
webclient/views.py - the django view serving the template (based on urls.py pattern)
src/server/portal/websockets.py - the server component talking to the client
The client is composed of two parts:
src/server/portal/websocket_client.py - the portal-side component
this file - the javascript component handling dynamic content
This implements an mud client for use with Evennia, using jQuery
for simplicity.
messages sent to the client is one of three modes:
OOB(func,(args), func,(args), ...) - OOB command executions
text - any other text is considered a normal text to echo
messages sent to the client is one of two modes:
OOB("func1",args, "func2",args, ...) - OOB command executions, this will
call unique javascript functions
func1(args), func2(args) etc.
text - any other text is considered a normal text output in the main output window.
*/
// jQuery must be imported by the calling html page before this script
// There are plenty of help on using the jQuery library on http://jquery.com/
// If on, allows client user to send OOB messages to server by
// prepending with ##OOB{}, for example ##OOB{"echo":[1,2,3,4]}
var OOB_debug = true
// Server communications
// Set this to the value matching settings.WEBSOCKET_PORTS
var wsurl = "ws://localhost:8001";
// Custom OOB functions
// functions defined here can be called by name by the server. For
// example the OOB{"echo":arguments} will trigger a function named
// echo(arguments).
function echo(message) {
// example echo function.
doShow("out", "ECHO return: " + message) }
// Webclient code
function webclient_init(){
// initializing the client once the html page has loaded
// called when client is just initializing
websocket = new WebSocket(wsurl);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
@ -34,91 +43,100 @@ function webclient_init(){
}
function onOpen(evt) {
// client is just connecting
// called when client is first connecting
$("#connecting").remove(); // remove the "connecting ..." message
msg_display("sys", "Using websockets - connected to " + wsurl + ".")
doShow("sys", "Using websockets - connected to " + wsurl + ".")
setTimeout(function () {
$("#playercount").fadeOut('slow', webclient_set_sizes);
$("#playercount").fadeOut('slow', doSetSizes);
}, 10000);
}
function onClose(evt) {
// client is closing
// called when client is closing
CLIENT_HASH = 0;
alert("Mud client connection was closed cleanly.");
}
function onMessage(evt) {
// outgoing message from server
// called when the Evennia is sending data to client
var inmsg = evt.data
if (inmsg.length > 3 && inmsg.substr(0, 3) == "OOB") {
// dynamically call oob methods, if available
try {var oobarray = JSON.parse(inmsg.slice(3));} // everything after OOB }
catch(err) {
// not JSON packed - a normal text
msg_display('out', err + " " + inmsg);
doShow('out', err + " " + inmsg);
return;
}
for (var ind in oobarray) {
try { window[oobarray[ind][0]](oobarray[ind][1]) }
catch(err) { msg_display("err", "Could not execute OOB function " + oobtuple[0] + "(" + oobtuple[1] + ")!") }
catch(err) { doShow("err", "Could not execute OOB function " + oobtuple[0] + "(" + oobtuple[1] + ")!") }
}
}
else {
// normal message
msg_display('out', inmsg); }
doShow('out', inmsg); }
}
function onError(evt) {
// client error message
msg_display('err', "Error: Server returned an error. Try reloading the page.");
// called on a server error
doShow('err', "Error: Server returned an error. Try reloading the page.");
}
function doSend(){
// sending data from client to server
// relays data from client to Evennia.
// If OOB_debug is set, allows OOB test data on the
// form ##OOB{func:args}
outmsg = $("#inputfield").val();
history_add(outmsg);
HISTORY_POS = 0;
$('#inputform')[0].reset(); // clear input field
if (outmsg.length > 4 && outmsg.substr(0, 5) == "##OOB") {
if (OOB_debug && outmsg.length > 4 && outmsg.substr(0, 5) == "##OOB") {
// test OOB messaging
doOOB(JSON.parse(outmsg.slice(5))); }
else {
// normal output
websocket.send(outmsg); }
}
function doOOB(oobdict){
// Handle OOB communication from client side
// Takes a dict on form {funcname:[args], funcname: [args], ... ]
msg_display("out", "into doOOB: " + oobdict)
msg_display("out", "stringify: " + JSON.stringify(oobdict))
// Send OOB data from client to Evennia.
// Takes input on form {funcname:[args], funcname: [args], ... }
var oobmsg = JSON.stringify(oobdict);
websocket.send("OOB" + oobmsg);
}
//
// OOB functions
//
function echo(message) {
msg_display("out", "ECHO return: " + message) }
//
// Display messages
function msg_display(type, msg){
// Add a div to the message window.
function doShow(type, msg){
// Add msg to the main output window.
// type gives the class of div to use.
// The default types are
// "out" (normal output) or "err" (red error message)
$("#messagewindow").append(
"<div class='msg "+ type +"'>"+ msg +"</div>");
// scroll message window to bottom
$('#messagewindow').animate({scrollTop: $('#messagewindow')[0].scrollHeight});
}
// Input history mechanism
function doSetSizes() {
// Sets the size of the message window
var win_h = $(document).height();
//var win_w = $('#wrapper').width();
var inp_h = $('#inputform').outerHeight(true);
//var inp_w = $('#inputsend').outerWidth(true);
$("#messagewindow").css({'height': win_h - inp_h - 1});
//$("#inputfield").css({'width': win_w - inp_w - 20});
}
//
// Input code
//
// Input history
var HISTORY_MAX_LENGTH = 21
var HISTORY = new Array();
@ -223,6 +241,8 @@ $.fn.appendCaret = function() {
});
};
// Input jQuery callbacks
$(document).keydown( function(event) {
// Get the pressed key (normalized by jQuery)
var code = event.which,
@ -233,7 +253,7 @@ $(document).keydown( function(event) {
// Special keys recognized by client
//msg_display("out", "key code pressed: " + code); // debug
//doShow("out", "key code pressed: " + code); // debug
if (code == 13) { // Enter Key
doSend();
@ -252,36 +272,24 @@ $(document).keydown( function(event) {
// handler to avoid double-clicks until the ajax request finishes
//$("#inputsend").one("click", webclient_input)
function webclient_set_sizes() {
// Sets the size of the message window
var win_h = $(document).height();
//var win_w = $('#wrapper').width();
var inp_h = $('#inputform').outerHeight(true);
//var inp_w = $('#inputsend').outerWidth(true);
// Callback function - called when the browser window resizes
$(window).resize(doSetSizes);
$("#messagewindow").css({'height': win_h - inp_h - 1});
//$("#inputfield").css({'width': win_w - inp_w - 20});
}
// Callback function - called when page has finished loading (gets things going)
// Callback function - called when page is closed or moved away from.
//$(window).bind("beforeunload", webclient_close);
//
// Callback function - called when page has finished loading (kicks the client into gear)
$(document).ready(function(){
// remove the "no javascript" warning, since we obviously have javascript
$('#noscript').remove();
// set sizes of elements and reposition them
webclient_set_sizes();
doSetSizes();
// a small timeout to stop 'loading' indicator in Chrome
setTimeout(function () {
webclient_init();
}, 500);
// set an idle timer to avoid proxy servers to time out on us (every 3 minutes)
setInterval(function() {
webclient_input("idle", true);
websocket.send("idle");
}, 60000*3);
});
// Callback function - called when the browser window resizes
$(window).resize(webclient_set_sizes);
// Callback function - called when page is closed or moved away from.
//$(window).bind("beforeunload", webclient_close);

View file

@ -19,9 +19,10 @@
<script language="javascript" type="text/javascript">
if ("WebSocket" in window) {
<!-- Importing the Evennia websocket webclient component (requires jQuery) -->
var wsurl = "{{websocket_url}}";
document.write("\<script src=\"/media/javascript/evennia_websocket_webclient.js\" type=\"text/javascript\" charset=\"utf-8\"\>\</script\>")}
else {
<!-- Importing the Evennia ajax webclient component (requires jQuery) -->
<!-- No websocket support in browser. Importing the Evennia ajax webclient component (requires jQuery) -->
document.write("\<script src=\"/media/javascript/evennia_ajax_webclient.js\" type=\"text/javascript\" charset=\"utf-8\"\>\</script\>")}
</script>
{% else %}

View file

@ -6,7 +6,6 @@
# tuple.
#
from django.db import models
from django.conf import settings
from src.utils.utils import get_evennia_version
@ -30,6 +29,9 @@ WEBSITE = ['Flatpages', 'News', 'Sites']
# The main context processor function
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
WEBSOCKET_CLIENT_ENABLED = settings.WEBSOCKET_CLIENT_ENABLED
WSURL = "%s:%s" % (settings.WEBSOCKET_CLIENT_URL, settings.WEBSOCKET_CLIENT_PORT)
def general_context(request):
"""
@ -44,6 +46,7 @@ def general_context(request):
'evennia_setupapps': GAME_SETUP,
'evennia_connectapps': CONNECTIONS,
'evennia_websiteapps':WEBSITE,
"webclient_enabled" : settings.WEBCLIENT_ENABLED,
"websocket_enabled" : settings.WEBSOCKET_ENABLED
"webclient_enabled" : WEBCLIENT_ENABLED,
"websocket_enabled" : WEBSOCKET_CLIENT_ENABLED,
"websocket_url" : WSURL
}