26. September 2010

10 years ago we spent weeks to develop a website chat. We implemented a chat server in C++, a PHP library, which talked to the chat server and JavaScript streaming in an iframe. Today it is much simpler.

Today we can use XMPP and BOSH and let the web page talk to my GTalk client, which runs all the time anyway.

For XMPP to work we need XMPP users. I prefer to run ejabberd with MySQL storage, because MySQL is the easiest way for me to add users and to manage the user list programatically. But the mnesia database also works.

Here is the config to use MySQL with ejabberd (to be added to ejabberd.cfg):

Download Strophe and jQuery (or use the CDN version http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js). Add references to the HTML-head:

<script type="text/javascript" src="jquery-1.4.2.min.js"></script>

<script type="text/javascript" src='strophe.min.js'></script>

4. Now comes the real fun: coding

We basically create a BOSH connection from Javascript to ejabberd through apache/mod_proxy:

var conn = new Strophe.Connection('/xmpp-httpbind');

Create an XMPP user in MySQL (I am using phpmyadmin) and connect with this user:

conn.connect('test@wolfspelz.de', 'secret', OnConnectionStatus);

The OnConnectionStatus function may look like:

function OnConnectionStatus(nStatus)

{

if (nStatus == Strophe.Status.CONNECTING) {

} else if (nStatus == Strophe.Status.CONNFAIL) {

} else if (nStatus == Strophe.Status.DISCONNECTING) {

} else if (nStatus == Strophe.Status.DISCONNECTED) {

} else if (nStatus == Strophe.Status.CONNECTED) {

OnConnected();

}

}

When the connection is established, register message handlers and send our own presence:

function OnConnected()

{

conn.addHandler(OnPresenceStanza, null, "presence");

conn.addHandler(OnMessageStanza, null, "message");

conn.send($pres());

}

BTW: handlers should always return "true". Otherwise they are removed from the handler list. A message handler may look like:

function OnMessageStanza(stanza)

{

var sFrom = $(stanza).attr('from');

var sType = $(stanza).attr('type');

var sBareJid = Strophe.getBareJidFromJid(sFrom);

var sBody = $(stanza).find('body').text();

// do something, e.g. show sBody with jQuery

return true;

}

A presence handler may be:

function OnPresenceStanza(stanza)

{

var sFrom = $(stanza).attr('from');

var sBareJid = Strophe.getBareJidFromJid(sFrom);

var sType = $(stanza).attr('type');

var sShow = $(stanza).find('show').text();

// do something, e.g. show status icon with jQuery

return true;

}

The connection should be closed when the page unloads. Unfortunately strophe.js (at least up to version 1.0.2) disconnects asynchronously, which does not work when the page is destroyed. After some time the XMPP server will notice, that the page disappeared and will close the connection. But if you do not want to wait, then we have to force strophe to close immediately.

There is a patch, which allows for synchronous connection closing. The patch must be applied to the strophe.js file:

Of course, all these functions and callbacks should be prototype based and bind the instance to the closure. We should also use a model-view architecture and handle the protocol stuff in the model, while notifying the view of really important events.

Here are the files:

contact.html - the "driver" which loads everything and produces the GUI

11. September 2010

Service URLs are URLs where you (the client) expect a service. No surprise. Examples:

the URL of a SOAP WSDL is a service URL,

the REST URL of the Twitter timeline API: http://twitter.com/statuses/friends_timeline.xml,

name or URL of AJAX scripts of a web site,

a chat room URL provides a software bus service.

These service URLs are provided to the client. Usually they are configured. The client gets them somehow. The client uses them to access the service. BUT the client should never construct them or append to them or change them. Service URLs should be final.

Why? I don't know, but I feel, that it is a bad thing. I had several cases in real applications where constructing, appending to, manipulating service URLs made an application less extensible, more complex, less testable.

Extensibility: if the client constructs the service URL from parts, then future server changes must take the client URL construction into account. The server can not just supply a different service URL, because the client also does something and might prevent the server from changing the service URL stucture or the URL altogether. For example, if the client appends the path of a URL to a host name and if the client assumes, that the server language is PHP, then the service URL might always look like a PHP script, even if the server technology changes. Imagine the unfortunate sysadmin who has to configure a Java application server to serve ".php" URLs. The client will have to be changed when the server changes. This is bad.

Complexity: Constructing URLs in the client is more processing than doing nothing. It introduces IFs, methods, more constants. This is usually not a big issue, but it rare cases the additional complexity can be quite significant. I have seen these cases.

Testability: Service URLs point to resources. Resources are dependencies. A very important concept of unit testing is inversion of control by dependency injection. But if the client generates its dependencies, then inversion of control is more difficult, if not impossible. It is much better to let the server be in control by configuring the client for production and test cases.

Do not...

combine host name and path to URLs

append or decide on filename extensions

append query parameters

decide the protocol

insert or remove port numbers

...in the client. Just leave it as you get it. The server knows what's good for you.