Tuesday 21 July 2009

Tutorial: Building a Comet Chat Application with StreamHub

Newly updated for version 2.0.2. Download 2.0.2 here.

Introduction

This tutorial will guide you through how to create a simple Comet chat application using the StreamHub Comet server. StreamHub is a lightweight Comet server used for streaming real-time data to web browsers and other clients. This tutorial assumes you know what Comet is and have some basic JavaScript and Java knowledge but it doesn't get too deep so don't worry. At the end of the tutorial we will have created a simple cross-browser chat application.

Step 1 - Download the Free Community Edition

First of all we need to download the StreamHub SDK from the website. Go to the StreamHub download page and grab yourself a copy of the Community Edition. At time of writing the latest version was 2.0.1 but any version should work. There isn't any real difference between the .ZIP version and the .TAR.GZ version - so grab which ever one you prefer. After downloading, extract the archive somewhere you can store it permanently. I put mine in C:\streamhub. Don't worry at this point if you're using Linux or Mac - the StreamHub server is completely cross platform.

Step 2 - Creating the chat page

We'll need a webpage to host the chat application so let's create that first. Create a new folder in your StreamHub directory called Chat, so now you'll have something like C:\streamhub\Chat. Create a new file in the Chat directory called index.html. This will be our Comet chat page. Edit that file, adding the following:
<html>
<head>
<title>Comet Chat with StreamHub</title>
</head>
<body>
<input type="text" id="chatText" onkeypress="checkForEnter(event)">
<input type="button" value="Chat" onclick="chat()">

<div id="chatMessages"></div>
<script>

function chat() {
// ...
}

function checkForEnter(e){
var e = e || event;
var key = e.keyCode || e.charCode;

if(key == 13){
chat();
}

return true;
}
</script>
</body>
</html>
All we've done is to create a very simple page with a textbox and a button labelled 'Chat'. We've added an onclick handler and an onkeypress handler. Don't worry too much about the checkForEnter() function, this just handles the user pressing enter in the textbox so the user can chat by pressing enter or clicking 'Chat'. The chatMessages div will hold any chat messages that we receive. We'll need to include the StreamHub JavaScript API, so lets do that now. The JavaScript API comes as a single mini-fied file called streamhub-min.js. Its in the archive we extracted in step 1, mine was in the directory C:\streamhub\streamhub-java-adapter-sdk-1.0\examples\lib\js. Copy this file and paste it in the Chat directory. We'll include it in the head of our chat page, so it should now look like:
<html>
<head>
<title>Comet Chat with StreamHub</title>
<script src="streamhub-min.js"></script>
</head>
<body>
<input type="text" id="chatText" onkeypress="checkForEnter(event)">
<input type="button" value="Chat" onclick="chat()">

<div id="chatMessages"></div>
<script>

function chat() {
// ...
}

function checkForEnter(e){
var e = e || event;
var key = e.keyCode || e.charCode;

if(key == 13){
chat();
}

return true;
}
</script>
</body>
</html>

Step 3 - Creating a Java Chat Adapter

Now we have a basic webpage setup we'll need to write some code in the backend to handle the receiving of messages and sending of messages to all connected chat clients. We'll create a Chat Java class to do this.

Create a new file in the chat directory called Chat.java, and add the following code to it:
import java.io.File;
import com.streamhub.api.PushServer;
import com.streamhub.nio.NIOServer;
import com.streamhub.api.Client;
import com.streamhub.api.PublishListener;
import com.streamhub.api.JsonPayload;
import com.streamhub.api.Payload;

public class Chat implements PublishListener {
private PushServer server;

public static void main(String[] args) throws Exception {
new Chat();
}

public Chat() throws Exception {
server = new NIOServer(7878);
server.addStaticContent(new File("."));
server.start();
server.getSubscriptionManager().addPublishListener(this);
System.out.println("Comet server started at http://localhost:7878/.");
System.out.println("Press any key to stop...");
System.in.read();
server.stop();
}

public void onMessageReceived(Client client, String topic, Payload payload) {
Payload chatMessage = new JsonPayload(topic);
chatMessage.addField("client", "Client-" + client.getUid());
chatMessage.addField("message", payload.getFields().get("message"));
server.publish(topic, chatMessage);
}
}
In the Chat class we've created a new Comet server and started it on port 7878:
server = new NIOServer(7878);
server.start();
We've then added our Chat class as a PublishListener:
server.getSubscriptionManager().addPublishListener(this);
This means that every time a client publishes something to the server we will get notified by a call to the onMessageReceived method. Later, in our chat webpage, we will publish every chat message to the server. So each chat message will come through to the onMessageReceived method. In this method we simply publish the chat message to every single user subscribed to the chat:
public void onMessageReceived(Client client, String topic, Payload payload) {
Payload chatMessage = new JsonPayload(topic);
chatMessage.addField("client", "Client-" + client.getUid());
chatMessage.addField("message", payload.getFields().get("message"));
server.publish(topic, chatMessage);
}
The client is an object representing the user who sent the chat message. The topic is the name of the topic they published on (we'll see later this is just "chat"). The payload is the JSON message that was published by the user. We will put each chat message in the field "message". We add two fields to the chatMessage payload: "client" which shows which user sent the chat message and "message" which shows the actual chat message. A UID is a unique identifier for each client, it gives each user a name, so we can see who said what. In a richer chat application would would probably add a way of having users choose there own name, but this will do for our simple example.

Step 4 - Connecting to the Server and Publishing Chat Messages

Now we have a Chat adapter, we need to add the code to our chat webpage to connect to the server and send the chat messages.

The code to connect looks like this:
var hub = new StreamHub();
hub.connect("http://localhost:7878/");
Now we are connected, we can subscribe to chat updates. We do this by passing a topic and a function which will be called every time a new chat message is received:
hub.subscribe("chat", chatUpdated);
We, define the chatUpdated() function elsewhere. Every data updated callback will receive two arguments: sTopic, the topic on which the update occurred and oData, a JSON object containing the fields which were updated. In the chatUpdated() function we extract the client who sent the chat message and the message itself and append it to the chatMessages div:
function chatUpdated(topic, data) {
var div = document.createElement("DIV");
div.innerHTML = data.client + ": " + data.message;
document.getElementById('chatMessages').appendChild(div);
}
Our chat webpage should now look like this:
<html>
<head>
<title>Comet Chat with StreamHub</title>
<script src="streamhub-min.js"></script>
</head>
<body>
<input type="text" id="chatText" onkeypress="checkForEnter(event)">
<input type="button" value="Chat" onclick="chat()">

<div id="chatMessages"></div>
<script>

function chatUpdated(topic, data) {
var div = document.createElement("DIV");
div.innerHTML = data.client + ": " + data.message;
document.getElementById('chatMessages').appendChild(div);
}

function chat() {
// ...
}

function checkForEnter(e){
var e = e || event;
var key = e.keyCode || e.charCode;

if(key == 13){
chat();
}

return true;
}

var hub = new StreamHub();
hub.connect("http://localhost:7878/");
hub.subscribe("chat", chatUpdated);
</script>
</body>
</html>
As you can see, we still haven't filled in what happens when the user clicks "Chat". In the chat() function we will need to send the users chat message to the server so it can be forwarded to every user subscirbed to the chat. We do this by using the publish method. The first argument to publish is the topic we want to publish on. The second argument is a string of arbitrary JSON. Whatever we pass to the publish method will be passed through to the onMessageReceived function in our Chat Java class. So we get the chat message out of the textbox and create a JSON object containing one field, the message:
function chat() {
var message = document.getElementById('chatText').value;
var json = "{'message':'" + escapeQuotes(message) + "'}";
hub.publish("chat", json);
}
Notice we call an escapeQuotes() method on the input from the textbox before we add it to the JSON. We do this because if the user happened to type a single quote it would mess up our JSON since each field is delimited by a single quote. Don't worry too much about the innards of the escapeQuotes() method - it just takes care of escaping any single quotes that would mess up the JSON. Now we're almost ready to try it out - the entire chat webpage should now look like this:
<html>
<head>
<title>Comet Chat with StreamHub</title>
<script src="streamhub-min.js"></script>
</head>
<body>
<input type="text" id="chatText" onkeypress="checkForEnter(event)">
<input type="button" value="Chat" onclick="chat()">

<div id="chatMessages"></div>
<script>

function chatUpdated(topic, data) {
var div = document.createElement("DIV");
div.innerHTML = data.client + ": " + data.message;
document.getElementById('chatMessages').appendChild(div);
}

function chat() {
var message = document.getElementById('chatText').value;
var json = "{'message':'" + escapeQuotes(message) + "'}";
hub.publish("chat", json);
}

function escapeQuotes(sString) {
return sString.replace(/(\')/gi, "\\$1").replace(/(\\\\\')/gi, "\\'");
}

function checkForEnter(e){
var e = e || event;
var key = e.keyCode || e.charCode;

if(key == 13){
chat();
}

return true;
}

var hub = new StreamHub();
hub.connect("http://localhost:7878/");
hub.subscribe("chat", chatUpdated);
</script>
</body>
</html>

Step 5 - Compiling the Chat Adapter and Starting Up

In this final step, we will compile the Chat class we created in Step 3, start it up and try out the chat application. Before we compile and start we need to copy over a few files.

We'll need a license, we can use the Free Community Edition license which comes in the archive we downloaded in Step 1. Copy the license.txt file over to the Chat directory. Mine was at C:\streamhub\streamhub-java-adapter-sdk-1.0\license.txt.

We'll also need a few JARs to compile and run. All the JARs come in the archive we extracted in Step 1. Mine were in the folder C:\streamhub\streamhub-java-adapter-sdk-1.0\examples\lib\jar. Copy the following JARs over to the Chat directory:
  • streamhub-VERSION.jar (e.g. streamhub-2.0.2.jar)
  • log4j-1.2.14.jar
  • json-20080701.jar
Lastly, so we can see some logging, copy over the whole conf folder from the archive to the Chat directory. Mine was found here: C:\streamhub\streamhub-java-adapter-sdk-1.0\conf.

Now, to compile, open a Command Prompt in the Chat directory and issue the following command:
javac -cp streamhub-2.0.2.jar Chat.java
To run, issue the following command:
java -cp json-20080701.jar;log4j-1.2.14.jar;streamhub-2.0.2.jar;. Chat
If you are using Unix (or Mac), classpath items are separated with ':' instead of ';', so you will need to run the following command instead of the above:
java -cp json-20080701.jar:log4j-1.2.14.jar:streamhub-2.0.2.jar:. Chat
That's it, navigate to http://localhost:7878/, type some text and chat away! Open up a couple of browser windows and see the Comet chat messages displayed in both windows.

That's the tutorial finished. The source code is available to download here: CometChat-2.0.2.zip. A good next step is to explore the examples that come with the Community Edition. We hope you found this useful - if you have any problems or suggestions for other guides and tutorials please comment.

9 comments:

  1. it' not working for me with 10 users. in fact, the from the 7th user, the 3 remaining users can't use the chat (community edition of course). and once the max users limit is reached, the server can't handle it to go back to less users. from that point your only option is to restart the server.

    ReplyDelete
  2. Hi,

    When I try to navigate the the URL, I get a 404, is there any way to resolve this?

    ReplyDelete
  3. Oops, i got it to load the page, but when I type some text and click on chat, what should happen, I don't see anything happening :(

    ReplyDelete
  4. Hi cappadochian, the StreamHub server used to count clients who disconnected recently as part of the max user count, because they are given an amount of time to reconnect if they had lost connection. As of version 2.0.10, these clients will not be counted so you should be able to get a full 10 users out of the community license a lot more easily.

    ReplyDelete
  5. @Vivek: If you're having trouble getting started, the best place to go for help is the StreamHub Comet Community: http://groups.google.co.uk/group/streamhub-comet-server-community

    ReplyDelete
  6. Hi
    Can you tell how to publish message to only specific clients? Currently server.publish(topic, chatMessage) publishes the chat message to all clients. I want the chatMessage to go to only some specific clients. Where do I put this restriction?

    ReplyDelete
  7. Hi Aniket,

    You can use the Client.send() method to send to specific clients. You can get access to the Client objects from the onMessageReceived or onSubscribe listener calls. For more details, refer to the javadoc links below:

    http://www.stream-hub.com/doc/2.0.15/javadoc/com/streamhub/api/Client.html#send(java.lang.String, com.streamhub.api.Payload)

    http://www.stream-hub.com/doc/2.0.15/javadoc/com/streamhub/api/PublishListener.html#onMessageReceived(com.streamhub.api.Client, java.lang.String, com.streamhub.api.Payload)

    http://www.stream-hub.com/doc/2.0.15/javadoc/com/streamhub/api/SubscriptionListener.html#onSubscribe(java.lang.String, com.streamhub.api.Client)

    ReplyDelete
  8. The way you connect changed in 2.1 onwards, those demos refer to
    2.0.x. To get the demo's working with 2.1+ all you need to do is
    change the connect statements from:

    hub.connect("http://localhost:7878/");

    ..to:

    hub.connect("http://localhost:7878/streamhub/");

    Also the stock ticker demo will not work unless you change line 16 to:

    private final Set symbols = new HashSet();

    ReplyDelete
  9. can any1 send how to send a message to a particular client? plz answer in detail.. i hav read the comment is writn abv bt cant understand how can i the recv id,sender id in chatlistener.java file?

    help me.!

    ReplyDelete