Cacomania: Node.js JSONP Chat server with a jQuery Mobile Client

Cacomania

Node.js JSONP Chat server with a jQuery Mobile Client

Guido Krömer - 22. November 2012 - Tags: ,

Welcome to the second part of my third Node.js http chat tutorial. This part of the tutorial describes the Node.js based server and makes use of the session handler which has been explained here. The HTML client gets discussed in the third part, when it's done.

The communication between the server, and the client is not in plain text, like in the WebSocket version, it makes use of JSON. Therefore the message data get encapsulated into the Message class or the two classes inherit from this class. For serialization the toJSON() is used which ensures that only data get send to the client and vice versa which are allowed to be public for everyone. Otherwise the session id of a specific user could get send to all clients, for example.

UML class diagramm of the node.js http chat server
The way of communicating with the clients using HTTP is limited to polling, the client has to ask the server for new messages. New messages get stored into the specific user object or into all if it's a public message. The ChatServer class creates a new http server for communicating with the clients and evaluates the messages and commands send by the clients.

this.addMessage = function (msg) {
    if (!this.loggedin) {
        return;
    }

    messageQueue.push(msg);
}

This is the User class addMessage() method which appends new messages to the messageQueue array if the user is logged in. When a client performs a request the server will call the specific users messages() method which returns all messages and empty the message queue array, if the user is logged in. The readMessages() method returns all messages without flushing the queue, it's there only for debugging purposes.

this.messages = function() {
    if (!this.loggedin) {
        return new Array(new Command('do_login'));
    }

    if (!messageQueue.length) {
        return messageQueue;
    }

    var tempQueue = messageQueue;
    messageQueue = new Array();
    return tempQueue;
}

Here is the less interesting part of the User class.

The ChatServer class task is, as mentioned before, handling all the client server communication. The HTTP server makes use of the SessionHandler to remember the clients. The code blow show the http server with the SessionHandler usage. If the sessions user object is not set a new user object gets created and assigned to the session. After that the request query gets checked for a command which would be stored into the "q" param. If the "q" param is set the message gets decoded and parsed before passing it to the perform() method, which handles the chat logic. The HTML client request the server via JSONP but the server itself can handle normal Ajax, too. The query's "ChatResponse" param contains the function name the client expects to receive the data via JSONP, if no "ChatResponse" param is set the server send the response as normal JSON.

module.exports.ChatServer = function (port, debug) {
...
var thath = this;
http.createServer(function (request, response) {
    var query = url.parse(request.url, true).query;
    var session = sessionHandler.getSession(request, response);

    if (!session.user) {
        session.user = new User('User_' + ++clientCounter, session);
    }

    if (query.q) {
        var clientMessage = JSON.parse(decodeURIComponent(query.q));
        thath.perform(session, clientMessage);
    }

    response.writeHead(200, {'Content-Type': 'application/x-javascript'});
    if (query.ChatResponse) {
        response.end(query.ChatResponse + '(' + JSON.stringify(session.user.messages()) + ');');
    }
    else {
        response.end(JSON.stringify(session.user.messages()));
    }
}).listen(port);
...
}

The messages send by a client gets redirected to the thath.perform() method. Depending of the type of the message the specific action gets executed. If the message is a command the command() method is going to handle this.

module.exports.ChatServer = function (port, debug) {
...
this.perform = function (session, msg) {
    var user = session.user;

    switch (msg.type) {
        case 'normal':
            this.addMessageToAll(new Message(msg.text, msg.type, user));
            break;
        case 'private':
            var recipient = this.getUserByName(msg.recipient);
            var privateMessage = new PrivateMessage(msg.text, user, recipient);
            recipient.addMessage(privateMessage);
            user.addMessage(privateMessage);
            break;
        case 'command':
            this.command(user, msg);
            break;
    }
}
...
}

Sending a message or command to all users can be done using the addMessageToAll(msg) method which call ends into a daisy chain ending with the session handler's forEachSession() method.

This is the whole ChatServer class.

Starting the server on a certain port is really simple, the MiniHttp Server started below for serving the client has been described here.

var ChatServer = require('./ChatServer.js').ChatServer
var chatServer = new ChatServer(8888)

var MiniHttp = require('./MiniHttp.js').MiniHttp;
var server = new MiniHttp('mobile_website/', 8000, null, 'app.html');

The whole code is available in the following gists: the chat server and the session handler. The third party explaining the jQuery Mobile client should come in the next days.