Pages

Showing posts with label CometD. Show all posts
Showing posts with label CometD. Show all posts

Thursday, June 6, 2013

Online server console viewer with Ajax long polling.

This post will give you small web application which brings the real time server console to the browser.  I am going to explain you, how to run this web application as it is and also if you want to add more customization, how to build the project using the source.

The Comet concept

This application a demonstrative usage of the Comet concept introduced by  Alex Russell in 2006. Once you get the idea, you can develop innovative applications and make use of it with your web application. For example, Facebook, it shows new friend requests, inbox updates and notifications on time. That is one of the real time usage of Comet approach.

The Comet is a web application model which always keeps live  connection with the server and let the server push the data to the browser through that connection. The browser does not explicitly request specific data. It just keeps live connection with the server. What ever the data pushed (or published) by the server are carried to the browser through that open connection. Also this concept is know as Ajax-Push, Reverse-Ajax, Two-way-web, HTTP Streaming, HTTP-server push.

There are several  ways of implementing Comet model. The CometD is a java framework  developed by Dojo Foundation which implements the Comet concept with 'XMLHttpRequest long polling'. This works with all modern browsers which supports XHR. With XHR long polling, the browser makes an asynchronous request to the server and if there are data available at the server, the response will bring that data to the browser as XML or JSON. After processing the response at client side, browser makes another XHR to poll more available data from the server.

If you have frequent data changes on the server and want to update browsers, the comet approach will be a best fit, because this can be pretty network intensive, and you often make pointless requests, though nothing has happened on the server. For example, frequent stock market information can be published to many users as soon as latest information is available. 

How the application works?

This application keeps reading the specified server log file line by line and push collected chunk of lines to the browser as a one string. By default, the application reads the Tomcat's catalina.out file. Server runs a timer which is set to every one second and executes these log file reading function. It collects  all the lines read within a one second and push to the client. In the client side, application keeps appending new contents to HTML DIV element. The server function will always reads only the newly appending lines of the the log file.

How to run the application?

If you are using Tomcat, you can experiment this application without much worrying. You just need to download the 'console-viewer.war' file and deploy it in your Tomcat container. If you are testing this with your local configuration, point your browser to following URL.

http://localhost:8080/console-viewer

Click on 'Connect' button and keep looking while you are accessing some deployed application. You will see the Tomcat's console on your web browser. Keep in mind, by default, this application reads 'catalina.out' file. So you need to start the Tomcat with ./startup.sh command. If you are appending your application's logs into different log file or using different server, I will explain, how to customize the application for that later. 

How to build the project with source?

I have provided complete source to you so that you can download the source code and go one step further or to read different log file or run with different server other then Tomcat. You can build the project using maven. If you are not using maven, you can create your own simple web application and copy the required contents. If you want to get the set of .jar files, you can get it from console-viewer.war file.

Here are the steps to build the project using maven.

Download the source and extract it to some where in your local disk. 

1. Navigate to the location.

    cd /home/semika/console-viewer

2. Build the project with the following command.

    mvn clean install

You can see, it builds the project and copies the 'console-viewer.war' file into CATALINA_HOME/webapps folder.  

3. Start the Tomcat with the following command.

   ./startup.sh

If you want to read some other log file rather than default 'catalina.out' file, or you want to run this application to view different server's console, you have to provide required log file's absolute path as an initialization parameter of 'Initializer' servlet as follows and deploy the application on the server.

<servlet>
    <servlet-name>initializer</servlet-name>
    <servlet-class>com.semika.cometd.Initializer</servlet-class>
    <init-param>
         <param-name>logFilePath</param-name>
         <param-value>
             /home/semika/apache-tomcat-7.0.37/logs/catalina.out
         </param-value>
     </init-param>
     <load-on-startup>2</load-on-startup>
</servlet>

Download the application and source

Download console-viewer.war file.
Download source code. If you encounter some issues with this, please send me a mail. I am always ready to give help.

Monday, October 15, 2012

Facebook similar chat for your Java web application.

Chatting is easy just like eating a piece of cake or drinking a hot coffee. Have you ever thought about developing a chat program by yourself?. You know that, it is not easy as chatting. But, if you are a developer and if you read to the end of this article, you may put a try to develop a chatting application by your self and allow your users to chat via your web application. 

I had to implement a chatting application for my web application. As every one does, I started to search on internet. I found IRC. When I read and search more about IRC, I understood that finding a web base client for IRC was difficult. I wanted to have more customizable web client which is working similar to Facebook. At last and luckily, I found CometD. 

Finally, I was able to implement chatting application by using CometD and more customizable chat windows opening on the browser which is exactly similar to Facebook. This works almost all the modern browsers. This article explains step by step, How to implement chatting application from the scratch and also How to integrate chatting application to your existing Java base web application. Remember, Your web application should be a Java base one.

You need to download the cometD from their official web site. It has all the dependencies required to implement the chatting application except tow java script libraries. I have written two Javascript libraries, one to create dynamic chat windows like Facebook and other to handle CometD chatting functionality in generic way. If you can manage these stuff by your self, you don't need to use those tow Javascript libraries. Actually, CometD documentation provides good details. But, I go ahead with the tutorial by using those tow libraries. Any way, I recommend first use those tow libraries and then customize it as you need. I hope to share the sample application with you and you can deploy it in your localhost and test, how it works.

1.Adding required jar files. 

If you use maven to build your project, add the following dependencies into your pom.xml file

<dependencies>
    <dependency>
        <groupId>org.cometd.java</groupId>
        <artifactId>bayeux-api</artifactId>
        <version>2.5.0</version>
    </dependency>
    <dependency>
         <groupId>org.cometd.java</groupId>
         <artifactId>cometd-java-server</artifactId>
         <version>2.5.0</version>
    </dependency>
    <dependency>
         <groupId>org.cometd.java</groupId>
         <artifactId>cometd-websocket-jetty</artifactId>
         <version>2.5.0</version>
         <exclusions>
             <exclusion>
                 <groupId>org.cometd.java</groupId>
                 <artifactId>cometd-java-client</artifactId>
             </exclusion>
         </exclusions>
   </dependency>
   <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.6.6</version>
   </dependency>
   <dependency>
        <groupId>org.cometd.java</groupId>
        <artifactId>cometd-java-annotations</artifactId>
        <version>2.5.0</version>
   </dependency>
</dependencies>

If you are not using maven to build your project, just copy the following .jar files into /WEB-INF/lib folder from your CometD download bundle. You can find these .jar files from /cometd-demo/target/cometd-demo-2.5.0.war file.

  • bayeux-api-2.5.0.jar
  • cometd-java-annotations-2.5.0.jar
  • cometd-java-common-2.5.0.jar
  • cometd-java-server-2.5.0.jar
  • cometd-websocket-jetty-2.5.0.jar
  • javax.inject-1.jar
  • jetty-continuation-7.6.7.v20120910.jar
  • jetty-http-7.6.7.v20120910.jar
  • jetty-io-7.6.7.v20120910.jar
  • jetty-jmx-7.6.7.v20120910.jar
  • jetty-util-7.6.7.v20120910.jar
  • jetty-websocket-7.6.7.v20120910.jar
  • jsr250-api-1.0.jar
  • slf4j-api-1.6.6.jar
  • slf4j-simple-1.6.6.jar

2.Adding required Javascript files.

You need to link the following Javascript files.

  • cometd.js
  • AckExtension.js
  • ReloadExtension.js
  • jquery-1.8.2.js
  • jquery.cookie.js
  • jquery.cometd.js
  • jquery.cometd-reload.js
  • chat.window.js
  • comet.chat.js
The 'chat.window.js' and 'comet.chat.js' are my own tow Javascript libraries which does not come with CometD distribution. If you are totally following this tutorial, you have to link those tow libraries as well. Provided sample application has these tow Javascript libraries. 

3.Writing chat service class.

/**
 * @author Semika siriwardana
 * CometD chat service.
 */
package com.semika.cometd;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.inject.Inject;

import org.cometd.annotation.Configure;
import org.cometd.annotation.Listener;
import org.cometd.annotation.Service;
import org.cometd.annotation.Session;
import org.cometd.bayeux.client.ClientSessionChannel;
import org.cometd.bayeux.server.BayeuxServer;
import org.cometd.bayeux.server.ConfigurableServerChannel;
import org.cometd.bayeux.server.ServerMessage;
import org.cometd.bayeux.server.ServerSession;
import org.cometd.server.authorizer.GrantAuthorizer;
import org.cometd.server.filter.DataFilter;
import org.cometd.server.filter.DataFilterMessageListener;
import org.cometd.server.filter.JSONDataFilter;
import org.cometd.server.filter.NoMarkupFilter;

@Service("chat")
public class ChatService {
    private final ConcurrentMap<String, Map<String, String>> _members = new ConcurrentHashMap<String, Map<String, String>>();
    @Inject
    private BayeuxServer _bayeux;
    @Session
    private ServerSession _session;

    @Configure ({"/chat/**","/members/**"})
    protected void configureChatStarStar(ConfigurableServerChannel channel) {
        DataFilterMessageListener noMarkup = new DataFilterMessageListener(new NoMarkupFilter(),new BadWordFilter());
        channel.addListener(noMarkup);
        channel.addAuthorizer(GrantAuthorizer.GRANT_ALL);
    }

    @Configure ("/service/members")
    protected void configureMembers(ConfigurableServerChannel channel) {
        channel.addAuthorizer(GrantAuthorizer.GRANT_PUBLISH);
        channel.setPersistent(true);
    }

    @Listener("/service/members")
    public void handleMembership(ServerSession client, ServerMessage message) {
        Map<String, Object> data = message.getDataAsMap();
        final String room = ((String)data.get("room")).substring("/chat/".length());
        
        Map<String, String> roomMembers = _members.get(room);
        if (roomMembers == null) {
            Map<String, String> new_room = new ConcurrentHashMap<String, String>();
            roomMembers = _members.putIfAbsent(room, new_room);
            if (roomMembers == null) roomMembers = new_room;
        }
        final Map<String, String> members = roomMembers;
        String userName = (String)data.get("user");
        members.put(userName, client.getId());
        client.addListener(new ServerSession.RemoveListener() {
            public void removed(ServerSession session, boolean timeout) {
                members.values().remove(session.getId());
                broadcastMembers(room, members.keySet());
            }
        });

        broadcastMembers(room, members.keySet());
    }

    private void broadcastMembers(String room, Set<String> members) {
        // Broadcast the new members list
        ClientSessionChannel channel = _session.getLocalSession().getChannel("/members/"+room);
        channel.publish(members);
    }

    @Configure ("/service/privatechat")
    protected void configurePrivateChat(ConfigurableServerChannel channel) {
        DataFilterMessageListener noMarkup = new DataFilterMessageListener(new NoMarkupFilter(),new BadWordFilter());
        channel.setPersistent(true);
        channel.addListener(noMarkup);
        channel.addAuthorizer(GrantAuthorizer.GRANT_PUBLISH);
    }

    @Listener("/service/privatechat")
    protected void privateChat(ServerSession client, ServerMessage message) {
        Map<String,Object> data = message.getDataAsMap();
        
        String room = ((String)data.get("room")).substring("/chat/".length());
        Map<String, String> membersMap = _members.get(room);
        if (membersMap == null) {
            Map<String,String>new_room=new ConcurrentHashMap<String, String>();
            membersMap=_members.putIfAbsent(room,new_room);
            if (membersMap==null)
                membersMap=new_room;
        }
        
        String peerName = (String)data.get("peer");
        String peerId = membersMap.get(peerName);
        
        if (peerId != null) {
            
         ServerSession peer = _bayeux.getSession(peerId);
            
            if (peer != null) {
             Map<String, Object> chat = new HashMap<String, Object>();
                String text = (String)data.get("chat");
                chat.put("chat", text);
                chat.put("user", data.get("user"));
                chat.put("scope", "private");
                chat.put("peer", peerName);
                ServerMessage.Mutable forward = _bayeux.newMessage();
                forward.setChannel("/chat/" + room);
                forward.setId(message.getId());
                forward.setData(chat);

                if (text.lastIndexOf("lazy") > 0) {
                    forward.setLazy(true);
                }
                if (peer != client) {
                    peer.deliver(_session, forward);
                }
                client.deliver(_session, forward);
            }
        }
    }

    class BadWordFilter extends JSONDataFilter {
        @Override
        protected Object filterString(String string) {
            if (string.indexOf("dang") >= 0) {
                throw new DataFilter.Abort();
            }
            return string;
        }
    }
}

4.Changing web.xml file.

You should add the following filter into your web.xml file.

<filter>
     <filter-name>continuation</filter-name>
     <filter-class>org.eclipse.jetty.continuation.ContinuationFilter</filter-class>
</filter>
<filter-mapping>
     <filter-name>continuation</filter-name>
     <url-pattern>/cometd/*</url-pattern>
</filter-mapping>

And also the following servlet.

<servlet>
    <servlet-name>cometd</servlet-name>
    <servlet-class>org.cometd.annotation.AnnotationCometdServlet</servlet-class>
    <init-param>
         <param-name>timeout</param-name>
         <param-value>20000</param-value>
    </init-param>
    <init-param>
         <param-name>interval</param-name>
         <param-value>0</param-value>
    </init-param>
    <init-param>
         <param-name>maxInterval</param-name>
         <param-value>10000</param-value>
    </init-param>
    <init-param>
         <param-name>maxLazyTimeout</param-name>
         <param-value>5000</param-value>
    </init-param>
    <init-param>
         <param-name>long-polling.multiSessionInterval</param-name>
         <param-value>2000</param-value>
    </init-param>
    <init-param>
         <param-name>logLevel</param-name>
         <param-value>0</param-value>
    </init-param>
    <init-param>
         <param-name>transports</param-name>
         <param-value>org.cometd.websocket.server.WebSocketTransport</param-value>
    </init-param>
    <init-param>
         <param-name>services</param-name>
         <param-value>com.semika.cometd.ChatService</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>cometd</servlet-name>
    <url-pattern>/cometd/*</url-pattern>
</servlet-mapping>

5.Implementing client side functions.

I think this section should be descriptive. If you allows your users to chat with other users, you need to show the list of online users in you web page, just like Facebook shows the online users inside the right side bar. For that, you can place a simple <span> or <div> tag inside your page. I have done it as follows.
<div id="members"></div>

All the online users will be displayed with in the above container. Once you click on a particular user name, it will open a new chat window similar to Facebook. For each pair of users, it will open a new chat window. To get this behaviour, you should use 'chat.window.js' which I mentioned before. Chatting in between particular pair of users will continue through a dedicated chat window. 

Just after user is logging into your web application as usual way, we should subscribe that user to chat channels. You can do it using the following way. 

 
$(document).ready(function(){ 
    $.cometChat.onLoad({memberListContainerID:'members'});
});

Note that, I have passed the 'id' of online user list container as a configuration parameter. Then, user should be joined with channel as follows.You can call the bellow method with the username.
function join(userName){
   $.cometChat.join(userName);
}

Since for each chat, there is a dedicated chat window just like Facebook, we should maintain global Javascript array to store those created chat window objects. You need to place the following Javascript code inside your page.
function getChatWindowByUserPair(loginUserName, peerUserName) {
    var chatWindow;  
    for(var i = 0; i < chatWindowArray.length; i++) {
       var windowInfo = chatWindowArray[i];
       if (windowInfo.loginUserName == loginUserName && windowInfo.peerUserName == peerUserName) {
           chatWindow =  windowInfo.windowObj;
       }
    }
    return chatWindow;
}
 
function createWindow(loginUserName, peerUserName) {  
    var chatWindow = getChatWindowByUserPair(loginUserName, peerUserName);
    if (chatWindow == null) { //Not chat window created before for this user pair.
       chatWindow = new ChatWindow(); //Create new chat window.
       chatWindow.initWindow({
             loginUserName:loginUserName, 
             peerUserName:peerUserName,
             windowArray:chatWindowArray});
   
       //collect all chat windows opended so far.
       var chatWindowInfo = { peerUserName:peerUserName, 
                              loginUserName:loginUserName,
                              windowObj:chatWindow 
                            };
   
       chatWindowArray.push(chatWindowInfo);
   }
   chatWindow.show();  
   return chatWindow;
}

As I mentioned above, declare following global Javascript variable.
var chatWindowArray = [];   
var config = {
    contextPath: '${pageContext.request.contextPath}'
};

Since I am using a JSP page, I have to get the context path via 'pageContext' variable. If you are using a HTML page, manage it by your self to declare 'config' Javascript global variable.
Now, you almost reached to last part of the tutorial.

5.How does the sample application works?

You can download the comet.war file and deploy it in your server. Point the browser to following URL.

http://localhost:8080/comet

This will bring you to a page which has a text field and button called "Join". Insert some user name as you wish and click on "Join" button. Then you will be forwarded to a another page which has list of online users. Your name is highlighted in red color. To chat in your local machine, You can open another browser (IE and FF) and join to the chat channel. The peer user displays in blue color in the online users list. Once you click on a peer user, it will open a new chat window so that You can chat with him. This functions very similar to Facebook chatting. 

I have tested this chatting application in IE, FF and Crome and works fine. If you want any help of integrating this with your Java base web application, just send me a mail.


You may also like:
Share

Widgets