Implementing Reverse Ajax (Comet) in Grails using Websocket and Atmosphere plugin

Posted By : Nagesh Chauhaan | 02-Jan-2013

Today I am going to share my findings on how to Integrate websocket in grails using Atmosphere plugin. Initially I just wanted to implement all reverse ajax and server push stuff , so I created a simple servlet based application . Later on I realise that if I could added this in a Grails project than it would be very usefull. After a little effort and a few hour of working I am able to integrate atmosphere with java to take advantage of websocket.

I started all this by creating a simple Grails project in STS. Than I added atmosphere plugin to take advantage of its flexible nature. After this I added some code from sample chat app that is given on atmosphere official site and modified the code according to my need. After a number of failure and retry finaly I was able to see expected result.

I used STS and the default server in it does not support websocket ,so i was forced to add apache tomcat to sts before I start runing my application.

Let me now explain some of code sample and changes that I have implemented to make all this working. Added a simple service to the application that is used to map URL so that js files can attach this to websocket side of code.

\grails-app\services\chat\ChatService.groovy

package oodles.grails.chatApp

import org.atmosphere.cpr.*
import org.atmosphere.cpr.AtmosphereResource.TRANSPORT
import grails.converters.JSON

class GrailsChatService {

    static transactional = false
    static atmosphere = [mapping: '/atmosphere/chatty']

    def onRequest = { event ->
        println "Inside onRequest!"
        try {
            AtmosphereRequest req = event.request
            if (req.method.equalsIgnoreCase("GET")) {
                println 'Suspending'
                event.suspend()
            } else if (req.method.equalsIgnoreCase("POST")) {
                event.broadcaster.broadcast(req.reader.readLine().trim())
            }
        } catch (Exception e) {
            println "ERROR!!!!!"
        }

    }

    def onStateChange = { event ->
        println "Inside onStateChange!"
        AtmosphereResource r = event.resource
        AtmosphereResponse res = r.response

        try {
            if (event.isSuspended()) {
                def msg = JSON.parse(event.message)
                res.writer.write( createMessage(msg.author, msg.message) )

                switch (r.transport()) {
                    case TRANSPORT.JSONP:
                    case TRANSPORT.LONG_POLLING:
                        event.resource.resume()
                        break
                    default:
                        res.writer.flush()
                }
            } else if (!event.isResuming()) {
                event.broadcaster().broadcast( createMessage('someone', 'buh bye') )
            }
        } catch (Exception e) {
            println "ERROR in onStateChange: $e"
        }
    }

    private String createMessage(String author, String text) {
        return new JSON( [text : text, author : author, time : new Date().time] )
    }
}

Methods onStateChange and onRequest handles are responsible for most of the application flow from server to client side. onRequest() is responsible to accept a intial request from browser to send alerts automatically.Once a sse connection is establised all other server push of data is done by onStateChange() , as a notification is received it is sent to browser in form of a message. Application starts from index.gsp ,this file looks something like this :

\grails-app\views\index.gsp

<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <meta name="layout" content="main">
    <title></title

</head>
<body>
<atmosphere:resources/>

<div id="header"><h3>Oodles | Websocket implementation in Grails</h3></div>
<div id="detect"><h3>Websocket Support Status</h3></div>
<div id="content"></div>
<div>
    <span id="status">Connecting...</span>
    <input type="text" id="input"/>
</div>
<div id="header"><h3>Oodles Technologies Pvt Ltd</h3></div>
</body>
</html>

\grails-app\conf\AtmosphereConfig.groovy

atmospherePlugin {
	servlet {

		initParams = [
					'org.atmosphere.cpr.cometSupport': 'org.atmosphere.container.Tomcat7CometSupport'
				]
		urlPattern = '/atmosphere/*'
	}
	handlers {

		atmosphereDotXml = {
		}
	}
}

\web-app\WEB-INF\applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<bean id="grailsApplication" class="org.codehaus.groovy.grails.commons.GrailsApplicationFactoryBean">
		>description>Grails application factory bean

	<bean id="grailsConfigurator" class="org.codehaus.groovy.grails.commons.spring.GrailsRuntimeConfigurator">
		<constructor-arg>
			<ref bean="grailsApplication" />
		</constructor-arg>
		<property name="pluginManager" ref="pluginManager" />
	</bean>

	<bean id="grailsResourceLoader" class="org.codehaus.groovy.grails.commons.GrailsResourceLoaderFactoryBean" />

	<bean id="characterEncodingFilter" class="org.springframework.web.filter.CharacterEncodingFilter">
		<property name="encoding">
			<value>utf-8>/value>
		</property>
	</bean>
</beans>

 

\web-app\WEB-INF\atmosphere-decorators.xml

<decorators>
    <excludes>
        <pattern>/atmosphere/*</pattern>
    </excludes>

\web-app\WEB-INF\sitemesh.xml

<sitemesh>
  <page-parsers>
    <parser content-type="text/html" class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser"/>
   <parser content-type="text/html;charset=ISO-8859-1" class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser"/>
    <parser content-type="text/html;charset=UTF-8" class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser"/>
  </page-parsers>
  <decorator-mappers>
    <mapper class="org.codehaus.groovy.grails.web.sitemesh.GrailsLayoutDecoratorMapper"/>
  </decorator-mappers>
  <excludes file="/WEB-INF/atmosphere-decorators.xml"/>
</sitemesh>

 

Once everything is configured we can get the application running like something like these screens.

In first picture we can check out the status of our server and browser if they support websockts or not . If websocket is supported by the browser than we have to add a name to start chat.

The very first value is used as name and after that all other values are used as a message. First a message is send to the server and after that the server pushes that message to all users.

In last figure we can see that a user named Sridhar is just online and he receives a message from other user Nagesh . This message is a result of server push only as sridhar does not request any message or data from server. Thats all about server push technology that can be implemented in Grails Projects easily.

There are a number of files in a grails project but they all are same and are automatically provided by STS. Here I have added those code files that need to be added explicitly.

 

 

 

 

Hope it helps !

Nagesh Chauhan

www.oodlestechnologies.com

About Author

Author Image
Nagesh Chauhaan

Request for Proposal

Name is required

Comment is required

Sending message..