JavaScript Sockets

Using Sockets with JavaScript

This tutorial will run through how to use sockets in JavaScript to build a simple chat program. There are many good tutorials already out there that show how to build a multi-user socket-based program with Haxe (e.g., multi-user-applications-in-haxe and flash-chat example) however they use Flash clients. Luckily, Haxe provides some good stuff, and so building a JavaScript client is relatively simple.

The code for this tutorial can be downloaded here: haxejssockets.zip. (The original post this tutorial spawned from is here)

The Architecture

The architecture of this program is heavily based on the example multi-user-applications-in-haxe with some names and code changed.

NekoServer is the chat server class. It extends neko.net.ThreadRemotingServer so it can use Haxe remoting. When a client connects, initClientApi is called, and NekoServer adds a ClientData object to represent the client. ClientData performs two functions. The first is that it stores a proxy to the client (ClientProxy), this is so we can called client-side functions from the server (i.e., any function from the ClientApi interface):

interface ClientApi {
    function setId( userId : Int ) : Void;
    function addUser( userId : Int ) : Void;
    function removeUser( userId : Int ) : Void;
    
    // called when a user says something
    function said( userId: Int, msg: String) : Void;
}

JSClient implements this interface. So when the server calls client.said(id,msg) the client simply prints the message on screen. The second important function that ClientData has is that it acts as a server proxy for the client. Whenever the client, connects, it is given a reference to the ClientData object, which implements the ServerApi:
interface ServerApi {
    function say(s:String): Void;
}

All this functionality is due to the awesomeness of Haxe Remoting.

The JavaScript Client

JSClient is an implementation of the client. (We could have different implementations of the client, as long as they implemented the ClientApi.) JSClient compiles to the JavaScript target, and runs within a webpage (ui.html).

To allow a continuous (socket-based) connection to the chat server, we need to use sockets. haxe.remoting.Socket will help us with this. Unfortunately, JavaScript does not yet have native sockets, and so we have to use a Flash object as a socket proxy. Any communication will then go from JavaScript through the Flash object to the server (and the other way round.) Fortunately, Haxe supports this, and so it is not to difficult to implement.

There are three main things we need to do:

1. Compile haxe.remoting.SocketWrapper into a Flash object
2. Embed the Flash object in the web page
3. Create a JavaScript socket that refers to the Flash object

Compiling SocketWrapper

In the compile.hxml file, we can see the commands to compile the socket wrapper are fairly simple:

-swf flashsocket.swf
haxe.remoting.SocketWrapper

Embedding the Flash object

The next step is to make sure the Flash object is loaded into the web page so that our JavaScript socket can use it. We can do this manually, by adding the necessary <embed> tags into the html. An easier way, however, is available in Haxe. Running the following code when our JSClient initialises will automatically embed the Flash object into our html page, and call it "flashsocket". (The 1,1 parameters give the Flash object a width and height of 1 pixel so we don't see it. It is also necessary to tell the Flash object that a script can access it.)

var swfo = new SWFObject("flashsocket.swf", "flashsocket", 1, 1, "9", "#ffffff"); 
swfo.addParam("allowScriptAccess", "always"); 
swfo.write("flashcontent");

Creating the JavaScript socket

The following code creates the JavaScript socket, sets up the connect and close callbacks, and connects to the NekoServer at localhost using port 4040.

me._socket = new haxe.remoting.Socket("flashsocket");
me._socket.onConnect = me.onConnect;
me._socket.onClose = me.onClose;
me._socket.connect( "localhost", 4040 );

Warning! If the Flash object is not initialised at this stage then you will receive an error, that looks like this: ExternalConnection is not initialized in Flash. In the example code, I add a small delay after creating the Flash object and before creating the Socket to get around this. A better solution would be to detect when the Flash object is loaded and then create the socket, or to simply catch the error and retry again and again.

The last bit of initialisation code, sets up Haxe Remoting, so the server can call the client api and so the client can call the server api.

var context = new haxe.remoting.Context();
context.addObject( "client", me );
var cnx = haxe.remoting.SocketConnection.create(me._socket,context);
me._server = new ServerProxy( cnx.ClientData );

The PolicyServer

For socket-based connections, Flash requires a "policy file" which gives it permission to connect to a server. There is some automatic policy file handling in Haxe, however I was unable to get it working in this example. Therefore, we have to manually set up a PolicyServer. Luckily, there was come code online to do this (included in this example as PolicyServer.hx). In a nutshell, the PolicyServer listens on port 843 and serves the policy file cross-domain.xml to the flashsocket, giving it permission to connect to NekoServer.

NekoServer.main creates a PolicyServer and a NekoServer, so all you have to do is execute neko server.n to run both servers.

Conclusion

It is unfortunate that in JavaScript land we have to use a Flash object as a proxy for socket-based communications. JavaScript will eventually have native support for sockets (websockets), however, at the moment many browsers don't implement this. When websockets are ubiquitous we can use those, but until then we have to use a Flash socket as a middleman.

version #9556, modified 2011-01-24 18:23:47 by jhl