Hey, I'm writing a book too :) Click the link below to preorder and save ;)
»» Get the Only Reference You'll "ever" Need on JavaScript Engineering Interviews, Now! ««
May 18th in Comet, Tutorial by .

Creating A Mobile JavaScript Chat – Part 1: The Service

A Comet Service

A Comet Service

In this series, we will be creating a simple JavaScript mobile chat application. It can be both used from a desktop browser as a web page, and from a handheld device as an application.

Keep in mind that this tutorial is just a demo, showing the bare bones of the process. The final application we create will be using text files as a persistence layer. It is not aimed to be used in a production environment.

Here are further things that can be done to make the app ready for production use.

  • Using nicknames (our demo simply posts text messages without indicating who sent what),
  • Using a database (or the cloud) as the persistence layer,
  • Creating some kind of registration, and guest access for non-registered memebers,
  • Timestamps for messages,
  • Showing only the messages that are sent after the user logs in (our demo will show everything old and new)
  • Private chats…

That’s just a minimum set :) Some other “nice to have”s include: Facebook connect, integrating other IM protocols such as MSN, or Yahoo!, sending and receiving files, sharing state across tabs and pages, using a robust chat server (such as ejabberd) to enable scaling up, video chat… The list can go on and on…

We may revisit our chat application in the future to add some of those features, who knows ;)

Anyway, every journey starts with a single step.
So let us begin our journey by creating a server interface:

Globals

Let’s define some globals that we’ll be using throughout the application.

//chat_common.php

// Global constants:

$CHAT_KEY_MESSAGE = 'message';
$CHAT_KEY_LAST_INDEX = 'lastIndex';
$CHAT_DATA_FILE_NAME = dirname(__FILE__) . '/data.txt';
$CHAT_CONNECTION_TIMEOUT = 25;

// Utility methods:

/**
 * Returns the value of matching key from $_GET,
 * returns the default value otherwise.
 */
function get($key, $default = '') {
	return isset($_GET[$key]) ? $_GET[$key] : $default;
}

Comet Busy-Wait Cycle

A Comet Service is nothing but a connection kept open unless something new happens:
Here’s a simple implementation that flushes the open connection either a timeout occurs, or some new data is added to the persistence layer (i.e. the data.txt file, in our case):

<?php
//chat_comet.php

include('./chat_common.php');

...

function page_load() {
	global $CHAT_DATA_FILE_NAME;
	global $CHAT_KEY_LAST_INDEX;
	global $CHAT_CONNECTION_TIMEOUT;
	
	//chat lines
	$lines = get_lines();
	// How many lines are there in total.
	$lineCount = count($lines);
	// The last sequence number retrieved in that session.
	$lastIndex = get($CHAT_KEY_LAST_INDEX, 0);
	// Used for detecting connection timeout.
	$startTime = $currentTime = time();
	// Used for detecting file changes.
	$startModified = $currentModified = filemtime($CHAT_DATA_FILE_NAME);

	$isNewData = ($lineCount - 1 > $lastIndex);	
	// if there is unread data, flush immediately.
	if($isNewData) {
		flush_buffer($lines, $lastIndex);
		die();
	}

	//busy-wait
	while(true) {
		// Give some breathing time for the CPU.
		usleep(10000);

		// Reset file statistics.
		clearstatcache();

		$currentTime = time();
		$currentModified = filemtime($CHAT_DATA_FILE_NAME);

		$isFileModified = ($currentModified - $startModified > 0);
		// Someone else has modified the file, get contents and flush.
		if($isFileModified) {
			$lines = get_lines();
			break;
		}

		$isResponseTimedOut = ($currentTime - $startTime > $CHAT_CONNECTION_TIMEOUT);
		// Nothing special, break and give a default response.
		if($isResponseTimedOut) {
			break;
		}
	}

	flush_buffer($lines, $lastIndex);
	die();
}

page_load();
?>

I believe the comments are self-explanatory.
The page_load function is fired on page load, as its name implies :)

And here are the helper functions get_lines and flush_buffer to complete:

function flush_buffer($lines, $lastIndex) {
	global $CHAT_KEY_LAST_INDEX;
	global $CHAT_KEY_MESSAGE;

	$response = array();
	$response[$CHAT_KEY_MESSAGE] = array_slice($lines, $lastIndex);
	$response[$CHAT_KEY_LAST_INDEX] = count($lines) - 1;

	echo json_encode($response);

	flush();
}

function get_lines() {
	global $CHAT_DATA_FILE_NAME;
	return  explode(PHP_EOL, file_get_contents($CHAT_DATA_FILE_NAME));
}

Sending a Message

That was for the “listening” part. But chat is a two-way communication and we need an endpoint to persist messages. That’s the easier part ;) :

//chat_send.php

/*
 * Sends a message via GET request.
 * chat_sendmessage.php?message={the_message}
 */

include('./chat_common.php');

// Add avnew message, if any.
$message = get($CHAT_KEY_MESSAGE, '');
if ($message != '')
{
	// Exclusively lock file to avoid race conditions.		
	file_put_contents($CHAT_DATA_FILE_NAME, $message.PHP_EOL, FILE_APPEND | LOCK_EX);
}

echo '1';
die();

Some Questions for the Curious

That’s all for this section. Hope it was useful :)

And here are some followup questions for further investigation:

  • Do you think the service can be improved? If so, how?
  • Any performance implications of keeping an HTTP connection open for a long time?
  • What is the best server (apache, nginx, lighthttpd, M$ IIS) for a comet service, and why?
  • Is this chat comet service secure? Well I don’t think so ;) .
    Definitely its vulnerable to an XSS injection.
    So how can we make it more secure? — any ideas?

In the next part of the series, we will be designing a chat client that consumes this comet service, and uses the chat_sendmessage endpoint to send a message.

In the meantime, feel free to share your comments, ideas and suggestions :)

It's me: Volkan! Jack of all Trades, Samurai of JavaScript ;) Since 2003, I’ve been doing front-end development on client-heavy AJAX web applications. Currently, I'm a JavaScript Hacker at "SocialWire". Before that I was a VP of Technology at "GROU.PS", a well-known do-it-yourself social networking platform that allows people to come together and form interactive communities around a shared interest or affiliation. Before that, I was a JavaScript Engineer at "LiveGO", a globally known social mash-up. Before that, I was the CTO of a business network ("cember.net") which was acquired by Xing AG for around 4.2M Euros. See my linkedin profile to find more about me; I also share worth-following bits an pieces on twitter.

VIsit Volkan Özçelik's website

9 Comments

Leave a Reply





o2.js _
Fork Ribbon