Chat example with Tora Queue in comet-style and Protocol

28.10.2013 3260 0

The server side :

The server part for the javascript client looks like that :

typedef Msg = { text : String, client : String };
class TestHandler extends tora.Handler {
	
	override function onNotify( v : Msg ) {
		neko.Lib.println( v.client + "#" + v.text );
		neko.Web.flush(); // ?
	}
}
 class Server {
	 
	static var q : tora.Queue<Msg> = tora.Queue.get( "chat" );
	
	static function main() {
		neko.Web.cacheModule( main );
		
		var params 	= neko.Web.getParams();
		var client	= params.get( "name" );
		
		if ( params.exists( "wait" ) ) {
			q.addHandler( new TestHandler() );
			// Fix first chuck > 1Kb
			var bytes	= haxe.io.Bytes.alloc( 1024 );
			neko.Lib.print( bytes.toString() );
			neko.Web.flush(); // ?
		}else {
			if( params.exists( "text" ) ){
				q.notify( { client : client, text : params.get( "text" ) } );
			}
		}
	}
}

The entry point checks the incoming parameters.
If a "wait" param is set, we add a handler, a kind of listener on the shared queue. Doing that, it blocks the request, that is kept alive. This persistant http connection will be used by the server to "push" data in chunks to the client.
When another request comes without the wait param, a message is dispatched on the queue and all handlers are notified. It means that all the kept-alive connections receive a chunk of data to transmit to the client.
Note : In order to get all that running fine from the first request, I had to add a first response chunck with a minimum of 1kb (I send here bytes but you can send what you want...).
Then I haven't really seen any difference with or without the flush function.

The Flash server side :

The same server side can be used both for the javascript and the flash client except that : None of these (1kb first chunk neither the flush function) are needed for the Flash side and Flash doesn't support the flush function, throws an error and disconnect.
So doing a simple modification by adding another param to the "wait" one, that defines the client type for example, we could then use the flush function or not depending on the handler's client type, and use exactly the same server both for Javascript and Flash clients .

The javascript client :

package ;
import js.Browser;
import js.html.DivElement;
import js.html.InputElement;
import js.html.Node;
import js.html.XMLHttpRequest;
/**
 * ...
 * @author filt3rek
 */
 class Client {
	 
	static var comet	: XMLHttpRequest;
	static var xhr		: XMLHttpRequest;
	static var name		: String;
	static var frame	: DivElement;
	
	static function main() {
		comet	= new XMLHttpRequest();
		xhr	= new XMLHttpRequest();
		name	= 'Guest' + Math.floor( Math.random() * 1000 );
		frame	= cast Browser.document.getElementById( 'frame' );
				
		comet.onreadystatechange = function ( e ) {
			switch( comet.readyState ) {
				case 3	:	// Progress
					refresh( comet.responseText );
				case 4	:	// Loaded	== Connection closed (timeout)
					haxe.Timer.delay( function (){
						if ( Browser.window.confirm( 'You have been disconnected from the server\nTry to reconnect ?' ) ) {
							connect();
						}
					}, 100 ); // Fix confirm freeze
			}
		}
				
		connect();
		
		var btn		= Browser.document.getElementById( 'btn');
		var tf		: InputElement	= cast Browser.document.getElementById( 'tf' );
		
		btn.onclick = function( e ) {
			send( 'text=${ tf.value }' );
		}
		
		tf.onkeypress	= function( e ) {
			if ( e.keyCode == 13 ) {
				send( 'text=${ tf.value }' );
				tf.value	= '';
			}
		}
	}
	
	static function refresh( s : String ) {
		var lines	= s.split( '\n' );
		for ( i in 0...lines.length ) {
			var line	= StringTools.trim( lines[ i ] );
			lines[ i ]	= line.split( '#' ).join( ' : ' );
		}
		
		frame.innerHTML	= lines.join( '<br />' );
		frame.scrollTop = frame.scrollHeight;
	}
	
	static function connect() {
		comet.open( 'POST', 'app.n', true );
		comet.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
		comet.send( 'wait=true' );
	}
	
	static function send( vars : String ) {
		xhr.open( 'POST', 'app.n', true );
		xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
		xhr.send( 'name=$name&$vars' );
	}
}

As we can see, 2 http connections are used : one for the comet and one to send one-shot requests.
The chunks from the server come in the 3rd XmlHttpRequest state (onreadystatechange callback). When 4th state is reached, it means the request has ended (the whole data is loaded), and the connection was probably closed so we can reconnect if needed.

The Flash part :

The Flash client is quite the same as the javascript one : It uses 2 connections, a http one-shot one, as with the javascript, and another (socket) that is a bit different because it's a direct persistant Flash-Tora server connection.

package ;
import flash.display.Sprite;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.Lib;
import flash.text.TextField;
import flash.text.TextFieldType;
import flash.text.TextFormat;
import haxe.Http;
import tora.Protocol;
/**
 * ...
 * @author filt3rek
 */
 class Client {
	 
	static function main() {
		
		var name	= 'Guest' + Math.floor( Math.random() * 1000 );
		
		var tfx 		= new TextFormat();
			tfx.size	= 12;
			tfx.font	= "Tahoma";
		
		var frame	= new TextField();
		var inputTF	= new TextField();
		var btnTF	= new TextField();
		var btn		= new Sprite();
		
		for ( i in [ frame, inputTF, btnTF ] ) {
			i.border		= true;
			i.borderColor		= 0x999999;
			i.defaultTextFormat	= tfx;
		}
		frame.multiline	= true;
		frame.wordWrap	= true;
		frame.width	= 600;
		frame.height	= 400;
				
		inputTF.type	= TextFieldType.INPUT;
		inputTF.y	= frame.y + frame.height + 5;
		inputTF.width	= 600;
		inputTF.height	= 20;
					
		btnTF.selectable		= false;
		btnTF.width		= 38;
		btnTF.height		= 20;
		btnTF.background	= true;
		btnTF.backgroundColor	= 0xCCCCCC;
		btnTF.text		= " send ";
		btnTF.mouseEnabled	= false;
		btn.x		= inputTF.x + inputTF.width + 5;
		btn.y		= inputTF.y;
		btn.buttonMode	= true;
		
		//
		
		var http	= new Http( "index.n" );
		http.addParameter( "name", name );
		
		var url = "http://torachat";
		
		var p = new Protocol( url );
		p.addParameter( "wait", "true" );
		
		p.onDisconnect = function() {
			frame.appendText( "DISCONNECTED\r\n" );
			//p.connect();
		};
		p.onError = function( e ) {
			frame.appendText( "ERROR " + e + "\r\n" );
			//p.connect();
		};
		p.onData = function( e ) {
			frame.appendText( e.split( "#" ).join( ' : ' ) );
		};
		
		//
		
		function send( text : String ) {
			http.setParameter( "text", text );
			http.request();
		}
		
		btn.addEventListener( MouseEvent.CLICK, function( e ) send( inputTF.text ) );
		inputTF.addEventListener( KeyboardEvent.KEY_DOWN, function( e : KeyboardEvent ) {
			if ( e.keyCode == 13 ) {
				send( inputTF.text );
				inputTF.text = '';
			}
		});
		
		btn.addChild( btnTF );
		for ( i in [ frame, inputTF, btn ] ) {
			Lib.current.addChild( i );
		}
		
		p.connect();
	}
}

The main difference is the usage of the Protocol class which connects to a specific host where the server (index.n) runs. For more informations about the Tora server commande line options please see here : http://ncannasse.fr/blog/tora_comet
This direct (socket) connection is important if you want to have a full real-time notification (especially for the client disconnection notification).

Chat example sources :

You can download the chat sources here (including HTMLs).
Then just open several browser windows with the HTML/javascript application or the Flash application, as you want, and check how it works
Note : For the Flash connection, you will need to configure a virtual host like explained in Nicolas' post.
Mine looks like that :

<VirtualHost *:6667>
    DocumentRoot /projects/__tests/tora/protocol/bin
    ServerName torachat
</VirtualHost>

Here, I have a host called torachat on my local machine that listen on port 6667 and the DocumentRoot parameter is the path to the server file index.n
Then the Tora server's command line :

haxelib run tora -unsafe torachat:6667 -config /apache2/conf/extra/httpd-vhosts.conf

Commentaires

Laisser un commentaire

http://
×