Converted webclient to use websockets on port 8001. Ideally one would make it so the ajax and websocket clients work under the same django wrapper, but for now this functionality is elusive.

This commit is contained in:
Griatch 2014-06-15 21:58:53 +02:00
parent 2a6cfaca7d
commit ede6634081
11 changed files with 326 additions and 37 deletions

View file

@ -262,6 +262,12 @@ if WEBSERVER_ENABLED:
web_root.putChild("webclientdata", webclient)
webclientstr = "/client"
#from src.server.portal import websocket
#factory = protocol.ServerFactory()
#websocketclient = websocket.WebSocketProtocol()
#websocketclient.sessionhandler = PORTAL_SESSIONS
#web_root.putChild("websocket", websocketclient)
web_root = server.Site(web_root, logPath=settings.HTTP_LOG_FILE)
proxy_service = internet.TCPServer(proxyport,
web_root,
@ -270,7 +276,6 @@ 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!

View file

@ -37,6 +37,7 @@ 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.

View file

@ -61,6 +61,14 @@ WEBSERVER_THREADPOOL_LIMITS = (1, 20)
# Start the evennia ajax client on /webclient
# (the webserver must also be running)
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]
# 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)
SSH_ENABLED = False
# Ports to use for SSH
@ -73,12 +81,6 @@ SSL_ENABLED = False
SSL_PORTS = [4001]
# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6.
SSL_INTERFACES = ['0.0.0.0']
# Activate Websocket support
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']
# 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.

View file

@ -0,0 +1,266 @@
/*
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
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
*/
// 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/
// Server communications
// Set this to the value matching settings.WEBSOCKET_PORTS
var wsurl = "ws://localhost:8001";
function webclient_init(){
websocket = new WebSocket(wsurl);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onerror = function(evt) { onError(evt) };
}
function onOpen(evt) {
$("#connecting").remove(); // remove the "connecting ..." message
msg_display("sys", "Using websockets - connected to " + wsurl + ".")
setTimeout(function () {
$("#playercount").fadeOut('slow', webclient_set_sizes);
}, 10000);
}
function onClose(evt) {
CLIENT_HASH = 0;
alert("Mud client connection was closed cleanly.");
}
function onMessage(evt) {
var inmsg = evt.data
if (inmsg.length > 3 && inmsg.substr(0, 3) == "OOB") {
// dynamically call oob methods, if available
try {var oobtuples = JSON.parse(inmsg.slice(3));} // everything after OOB }
catch(err) {
// not JSON packed - a normal text
msg_display('out', inmsg);
return;
}
for (var oobtuple in oobtuples) {
try { window[oobtuple[0]](oobtuple[1]) }
catch(err) { msg_display("err", "Could not execute OOB function " + oobtuple[0] + "!") }
}
}
else {
// normal message
msg_display('out', inmsg); }
}
function onError(evt) {
msg_display('err', "Error: Server returned an error. Try reloading the page.");
}
function doSend(){
outmsg = $("#inputfield").val();
history_add(outmsg);
HISTORY_POS = 0;
$('#inputform')[0].reset(); // clear input field
websocket.send(outmsg);
}
function doOOB(ooblist){
// Takes an array on form [funcname, [args], funcname, [args], ... ]
var oobmsg = JSON.stringify(ooblist);
websocket.send("OOB" + oobmsg);
}
//
// Display messages
function msg_display(type, msg){
// Add a div to the message window.
// type gives the class of div to use.
$("#messagewindow").append(
"<div class='msg "+ type +"'>"+ msg +"</div>");
// scroll message window to bottom
$('#messagewindow').animate({scrollTop: $('#messagewindow')[0].scrollHeight});
}
// Input history mechanism
var HISTORY_MAX_LENGTH = 21
var HISTORY = new Array();
HISTORY[0] = '';
var HISTORY_POS = 0;
function history_step_back() {
// step backwards in history stack
HISTORY_POS = Math.min(++HISTORY_POS, HISTORY.length-1);
return HISTORY[HISTORY.length-1 - HISTORY_POS];
}
function history_step_fwd() {
// step forward in history stack
HISTORY_POS = Math.max(--HISTORY_POS, 0);
return HISTORY[HISTORY.length-1 - HISTORY_POS];
}
function history_add(input) {
// add an entry to history
if (input != HISTORY[HISTORY.length-1]) {
if (HISTORY.length >= HISTORY_MAX_LENGTH) {
HISTORY.shift(); // kill oldest history entry
}
HISTORY[HISTORY.length-1] = input;
HISTORY[HISTORY.length] = '';
}
}
// Catching keyboard shortcuts
$.fn.appendCaret = function() {
/* jQuery extension that will forward the caret to the end of the input, and
won't harm other elements (although calling this on multiple inputs might
not have the expected consequences).
Thanks to
http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
for the good starting point. */
return this.each(function() {
var range,
// Index at where to place the caret.
end,
self = this;
if (self.setSelectionRange) {
// other browsers
end = self.value.length;
self.focus();
// NOTE: Need to delay the caret movement until after the callstack.
setTimeout(function() {
self.setSelectionRange(end, end);
}, 0);
}
else if (self.createTextRange) {
// IE
end = self.value.length - 1;
range = self.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', end);
// NOTE: I haven't tested to see if IE has the same problem as
// W3C browsers seem to have in this context (needing to fire
// select after callstack).
range.select();
}
});
};
$.fn.appendCaret = function() {
/* jQuery extension that will forward the caret to the end of the input, and
won't harm other elements (although calling this on multiple inputs might
not have the expected consequences).
Thanks to
http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
for the good starting point. */
return this.each(function() {
var range,
// Index at where to place the caret.
end,
self = this;
if (self.setSelectionRange) {
// other browsers
end = self.value.length;
self.focus();
// NOTE: Need to delay the caret movement until after the callstack.
setTimeout(function() {
self.setSelectionRange(end, end);
}, 0);
}
else if (self.createTextRange) {
// IE
end = self.value.length - 1;
range = self.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', end);
// NOTE: I haven't tested to see if IE has the same problem as
// W3C browsers seem to have in this context (needing to fire
// select after callstack).
range.select();
}
});
};
$(document).keydown( function(event) {
// Get the pressed key (normalized by jQuery)
var code = event.which,
inputField = $("#inputfield");
// always focus input field no matter which key is pressed
inputField.focus();
// Special keys recognized by client
//msg_display("out", "key code pressed: " + code); // debug
if (code == 13) { // Enter Key
doSend();
event.preventDefault();
}
else {
if (code == 38) { // arrow up 38
inputField.val(history_step_back()).appendCaret();
}
else if (code == 40) { // arrow down 40
inputField.val(history_step_fwd()).appendCaret();
}
}
});
// 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);
$("#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)
$(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();
// 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);
}, 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

@ -14,12 +14,12 @@
{% if app_list %}
{% for app in app_list %}
{% for app in app_list %}
{% if app.name in evennia_userapps %}
{% if app.name == 'Players' %}
<h2>Admin</h2>
<h2>Admin</h2>
<p><i>Players are the out-of-character representation of a
game account. A Player can potentially control any number of
in-game character Objects (depending on game).</i></p>
@ -49,7 +49,7 @@
<td>&nbsp;</td>
{% endif %}
</tr>
{% endif %}
{% endif %}
{% endfor %}
</table>
@ -58,15 +58,15 @@
{% endif %}
{% endfor %}
<h2>Game entities</h2>
<h2>Game entities</h2>
{% for app in app_list %}
{% for app in app_list %}
{% if app.name in evennia_entityapps %}
{% if app.name == 'Comms' %}
{% if app.name == 'Comms' %}
<p><i>This defines entities that has an in-game precense or
effect of some kind.</i></p>
effect of some kind.</i></p>
{% endif %}
<div class="module">
@ -99,18 +99,18 @@
{% endif %}
{% endfor %}
<h2>Website</h2>
{% for app in app_list %}
<h2>Website</h2>
{% for app in app_list %}
{% if app.name in evennia_websiteapps %}
{% if app.name == 'Flatpages' %}
{% if app.name == 'Flatpages' %}
<p><i>Miscellaneous objects related to the running and
managing of the Web presence.</i></p>
managing of the Web presence.</i></p>
{% endif %}
<div class="module">

View file

@ -9,13 +9,26 @@
<link rel='stylesheet' type="text/css" media="screen" href="/media/css/webclient.css">
<!-- Importing the online jQuery javascript library -->
<script src="http://code.jquery.com/jquery-1.6.1.js" type="text/javascript" charset="utf-8"></script>
<!--script src="http://code.jquery.com/jquery-1.6.1.js" type="text/javascript" charset="utf-8"></script-->
<script src="http://code.jquery.com/jquery-1.11.1.min.js" type="text/javascript" charset="utf-8"></script>
<!--for offline testing, download the jquery library from jquery.com-->
<!--script src="/media/javascript/jquery-1.4.4.js" type="text/javascript" charset="utf-8"></script-->
<!--script src="/media/javascript/jquery-1.11.1.js" type="text/javascript" charset="utf-8"></script-->
{% if websocket_enabled %}
<script language="javascript" type="text/javascript">
if ("WebSocket" in window) {
<!-- Importing the Evennia websocket webclient component (requires jQuery) -->
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) -->
document.write("\<script src=\"/media/javascript/evennia_ajax_webclient.js\" type=\"text/javascript\" charset=\"utf-8\"\>\</script\>")}
</script>
{% else %}
<!-- websocket not enabled; use ajax -->
<script src="/media/javascript/evennia_ajax_webclient.js" type="text/javascript" charset="utf-8"></script>
{% endif %}
<!-- Importing the Evennia ajax webclient component (requires jQuery) -->
<script src="/media/javascript/evennia_webclient.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
@ -38,7 +51,7 @@
</div>
<form id="inputform" action="javascript:void(0);">
<div id="playercount">Logged in Players: {{num_players_connected}}</div>
<div id="inputcontrol"><input type="text" id="inputfield" autocomplete="off"><input id="inputsend" type="button" value="send" onClick="webclient_input()" /></div>
<div id="inputcontrol"><input type="text" id="inputfield" autocomplete="off"><input id="inputsend" type="button" value="send" onClick="doSend()" /></div>
</form>
</div>

View file

@ -44,8 +44,8 @@ urlpatterns = patterns('',
# favicon
url(r'^favicon\.ico$', RedirectView.as_view(url='/media/images/favicon.ico')),
# ajax stuff
url(r'^webclient/',include('src.web.webclient.urls')),
# webclient stuff
url(r'^webclient/', include('src.web.webclient.urls')),
)
# This sets up the server if the user want to run the Django

View file

@ -1,9 +1,9 @@
#
# This file defines global variables that will always be
# This file defines global variables that will always be
# available in a view context without having to repeatedly
# include it. For this to work, this file is included in
# the settings file, in the TEMPLATE_CONTEXT_PROCESSORS
# tuple.
# include it. For this to work, this file is included in
# the settings file, in the TEMPLATE_CONTEXT_PROCESSORS
# tuple.
#
from django.db import models
@ -19,8 +19,8 @@ except AttributeError:
SERVER_VERSION = get_evennia_version()
# Setup lists of the most relevant apps so
# the adminsite becomes more readable.
# Setup lists of the most relevant apps so
# the adminsite becomes more readable.
PLAYER_RELATED = ['Players']
GAME_ENTITIES = ['Objects', 'Scripts', 'Comms', 'Help']
@ -44,5 +44,6 @@ def general_context(request):
'evennia_setupapps': GAME_SETUP,
'evennia_connectapps': CONNECTIONS,
'evennia_websiteapps':WEBSITE,
"webclient_enabled" : settings.WEBCLIENT_ENABLED
"webclient_enabled" : settings.WEBCLIENT_ENABLED,
"websocket_enabled" : settings.WEBSOCKET_ENABLED
}

View file

@ -8,7 +8,7 @@ page and serve it eventual static content.
from django.shortcuts import render_to_response, redirect
from django.template import RequestContext
from django.conf import settings
from src.server.sessionhandler import SESSIONS
from src.players.models import PlayerDB
def webclient(request):
"""
@ -21,8 +21,9 @@ def webclient(request):
print "Called from port 8000!"
#return redirect("http://localhost:8001/webclient/", permanent=True)
nsess = len(PlayerDB.objects.get_connected_players()) or "none"
# as an example we send the number of connected players to the template
pagevars = {'num_players_connected': SESSIONS.player_count()}
pagevars = {'num_players_connected': nsess}
context_instance = RequestContext(request)
return render_to_response('webclient.html', pagevars, context_instance)

View file

@ -33,7 +33,7 @@ def page_index(request):
nplyrs_conn_recent = len(recent_users) or "none"
nplyrs = PlayerDB.objects.num_total_players() or "none"
nplyrs_reg_recent = len(PlayerDB.objects.get_recently_created_players()) or "none"
nsess = len(PlayerDB.objects.get_connected_players()) or "noone"
nsess = len(PlayerDB.objects.get_connected_players()) or "none"
nobjs = ObjectDB.objects.all().count()
nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude(db_typeclass_path=_BASE_CHAR_TYPECLASS).count()