Search code examples
grailscometgrails-plugincometd

Why am I getting multiple-client: true advice from cometd when only 1 client is connected?


I'm working on integrating CometD into a Grails 2.0 application (By creating a new plugin - I'm not using the existing cometd plugin as it seems to be pretty fractured right now). I believe that I've got everything configured properly. What I'm experiencing is that when the browser connects, the initial meta/handshake comes back successful. However, every meta/connect call after that comes back with the advice for multiple-client set to true, closing the long-polling connection. This then happens every 2 seconds (the default interval I believe).

As I said, I'm using Grails 2.0, with tomcat 7 which is what Grails uses by default.

Here's what I'm currently seeing:

Call to /meta/handshake

Request URL:http://localhost:8080/cometd/handshake
Request Method:POST
Status Code:200 OK

Request Headers
Accept:*/*
Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:182
Content-Type:application/json;charset=UTF-8
Host:localhost:8080
Origin:http://localhost:8080
Referer:http://localhost:8080/
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.46 Safari/535.11
X-Requested-With:XMLHttpRequest

Request Payload
[{"version":"1.0","minimumVersion":"0.9","channel":"/meta/handshake","supportedConnectionTypes":["long-polling","callback-polling"],"advice":{"timeout":60000,"interval":0},"id":"1"}]

Response Headers
Content-Length:201
Content-Type:application/json;charset=UTF-8
Date:Tue, 14 Feb 2012 03:32:24 GMT
Server:Apache-Coyote/1.1
Set-Cookie:BAYEUX_BROWSER=dac8yl3vlli7s8x2gymdm2ca177s; Path=/

This is the return payload:

[{"id":"1","minimumVersion":"1.0","supportedConnectionTypes":["callback-polling","long-polling"],"successful":true,"channel":"/meta/handshake","clientId":"115v93y19uvavr159qvzz43yct1","version":"1.0"}]

I have verified that the cookie is being set in the browser. The subsequent calls to /meta/connect look something like this:

Request URL:http://localhost:8080/cometd/connect
Request Method:POST
Status Code:200 OK

Request Headers
Accept:*/*
Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:134
Content-Type:application/json;charset=UTF-8
Cookie:BAYEUX_BROWSER=dac8yl3vlli7s8x2gymdm2ca177s
Host:localhost:8080
Origin:http://localhost:8080
Referer:http://localhost:8080/
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.46 Safari/535.11
X-Requested-With:XMLHttpRequest

Request Payload
[{"channel":"/meta/connect","connectionType":"long-polling","advice":{"timeout":0},"id":"2","clientId":"115v93y19uvavr159qvzz43yct1"}]

Response Headers
Content-Length:116
Content-Type:application/json;charset=UTF-8
Date:Tue, 14 Feb 2012 03:32:24 GMT
Server:Apache-Coyote/1.1

with the content-length and id in the request payload changed per each request. The first 3 calls come back with this:

call 1:

[{"id":"2","successful":true,"advice":{"interval":0,"reconnect":"retry","timeout":30000},"channel":"/meta/connect"}]

call 2:

[{"id":"3","successful":true,"channel":"/meta/connect"}]

call 3:

[{"id":"4","successful":true,"advice":{"interval":2000,"reconnect":"retry","multiple-clients":true},"channel":"/meta/connect"}]

From that point, every message that comes back is exactly like the last. I only have one browser window/tab open.

Here's a copy of the applicable parts of the web.xml file:

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="3.0" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
...
<servlet>
    <servlet-name>cometd</servlet-name>
    <servlet-class>org.cometd.server.CometdServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
    <init-param>
        <param-name>timeout</param-name>
        <param-value>30000</param-value>
    </init-param>
    <init-param>
        <param-name>logLevel</param-name>
        <param-value>3</param-value>
    </init-param>
</servlet>
...
<servlet-name>cometd</servlet-name>
    <url-pattern>/cometd/*</url-pattern>
</servlet-mapping>
...
</web-app>

The BuildConfig.groovy:

grails.project.class.dir = "target/classes"
grails.project.test.class.dir = "target/test-classes"
grails.project.test.reports.dir = "target/test-reports"
grails.project.target.level = 1.6

grails.project.dependency.resolution = {
    // inherit Grails' default dependencies
    inherits "global"
    log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
    repositories {
        mavenCentral()
    }
    dependencies {

        def cometVersion = '2.4.0'

        compile(group: 'org.cometd.java', name: 'cometd-java-server', version: cometVersion) {
            excludes 'servlet-api'
        }
        compile group: 'org.cometd.java', name: 'bayeux-api', version: cometVersion
        runtime group: 'org.eclipse.jetty', name: 'jetty-servlets', version: '7.6.0.v20120127'
        test group: 'org.cometd.java', name: 'cometd-java-client', version: cometVersion        
    }

    plugins {
        build(":tomcat:$grailsVersion",
            ":release:1.0.0") {
            export = false
        }
    }
}

Finally the doWithSpring section of the CometdGrailsPlugin.groovy:

def doWithSpring = {

    bayeux(BayeuxServerImpl) { bean ->
        bean.initMethod = 'start'
        bean.destroyMethod = 'stop'
    }

    // the CometdServlet will pick up the Bayeux object from the servlet context
    bayeuxAttributeExporter(ServletContextAttributeExporter) {
        attributes = [(BayeuxServer.ATTRIBUTE): ref('bayeux')]
    }        
}

Hopefully this isn't too much information. I've been trying to figure this out for the past few days, and have run out of ideas and could really use some help


Solution

  • It appears that this was related to a bug in Grails 2.0.0. After upgrading to Grails 2.0.1 (release today) the error resolved itself.