Hot questions for Using GlassFish in websocket

Question:

I am trying to send binary message encoded using protocol buffers through websocket. I am able to successfully send using Glassfish 4.0. But the same code fails in tomcat 8 with exception below.

java.lang.IllegalArgumentException
at java.nio.Buffer.limit(Buffer.java:267)
at org.apache.tomcat.websocket.PerMessageDeflate.sendMessagePart(PerMessageDeflate.java:368)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.startMessage(WsRemoteEndpointImplBase.java:297)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.startMessageBlock(WsRemoteEndpointImplBase.java:270)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.sendBytes(WsRemoteEndpointImplBase.java:132)
at org.apache.tomcat.websocket.WsRemoteEndpointBasic.sendBinary(WsRemoteEndpointBasic.java:43)
at com.trsim.sim.endpoint.Whiteboard.broadcastSnapshot(Whiteboard.java:164)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBase.onMessage(PojoMessageHandlerWholeBase.java:80)
at org.apache.tomcat.websocket.WsFrameBase.sendMessageBinary(WsFrameBase.java:586)
at org.apache.tomcat.websocket.WsFrameBase.processDataBinary(WsFrameBase.java:543)
at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:295)
at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:130)
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:60)
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler$WsReadListener.onDataAvailable(WsHttpUpgradeHandler.java:203)
at org.apache.coyote.http11.upgrade.AbstractServletInputStream.onDataAvailable(AbstractServletInputStream.java:194)
at org.apache.coyote.http11.upgrade.AbstractProcessor.upgradeDispatch(AbstractProcessor.java:96)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:654)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:223)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1558)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1515)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:744)

Below is my java implementation for handling a binary message and send it back

 @OnMessage
        public void broadcastSnapshot(ByteBuffer data, Session session) throws IOException {
            //I am able to parse the incoming binary header data
            Header incomingHeader = Header.parseFrom(data.array());
            Header header = Header.newBuilder().setCallback("2").setDecodeusing("MyClasstodecode").build();
            //I am able to parse the incoming data
            AddressBook incomingAddressBook = AddressBook.parseFrom(data.array());


        //Creating Ouput messages
        Person john =
                  Person.newBuilder()
                    .setId(1234)
                    .setName("John Doe")
                    .setEmail("jdoe@example.com")
                    .addPhone(
                      Person.PhoneNumber.newBuilder()
                        .setNumber("555-4321")
                        .setType(Person.PhoneType.HOME))
                    .build();
        Person keith =
                Person.newBuilder().setId(1234).setName("John Doe").setEmail("jdoe@example.com").addPhone(Person.PhoneNumber.newBuilder().setNumber("555-4321").setType(Person.PhoneType.HOME)).build();
        AddressBook.Builder addressBook = AddressBook.newBuilder();
        addressBook.addPerson(john);
        addressBook.addPerson(keith);


        //Converting output message to byte array using Protocol buffers
        byte[] headerArray = header.toByteArray();
        byte[] byteBuffer = addressBook.build().toByteArray();


        ByteBuffer outputBuffer = ByteBuffer.allocate(byteBuffer.length + headerArray.length );
        outputBuffer.put(headerArray,0,headerArray.length);
        outputBuffer.put(byteBuffer,0,byteBuffer.length);

        try{
            //Send the received message first -- fails in tomcat but works in glassfish 4
            ByteBuffer buffer = ByteBuffer.allocate(incomingAddressBook.toByteArray().length).put(incomingAddressBook.toByteArray());
            session.getBasicRemote().sendBinary(buffer);

            //Send the response -- fails in tomcat.. But works in glassfish 4
            session.getBasicRemote().sendBinary(outputBuffer);
        }catch(Exception e){
            e.printStackTrace();
        }
    }

Answer:

Looks like you need to check the buffer size of tomcat server configuration. The default buffer size for binary messages is 8192 bytes. See WebSocket Howto

What is the value of incomingAddressBook.toByteArray().length ?

Try the below in your web.xml.,

<context-param>
     <param-name>org.apache.tomcat.websocket.textBufferSize</param-name>
     <param-value>32768</param-value>
</context-param>
<context-param>
     <param-name>org.apache.tomcat.websocket.binaryBufferSize</param-name>
     <param-value>32768</param-value>
</context-param>

Also, use the below.,

ByteBuffer buffer = ByteBuffer.allocate(incomingAddressBook.toByteArray().length).put(incomingAddressBook.toByteArray());
buffer.flip(); //before sending message
session.getBasicRemote().sendBinary(buffer);

Question:

Good evening,

I have a simple websocket application. It runs fine if I deploy it to standalone glassfish server either using asadmin command or my IDE. When I use glassfish-embedded, everything seems fine (no error messages), but I get 404 at endpoint which should be serving websockets.

WebSocket connection to 'ws://localhost:8080/test/websocket/zelitomasfds/efdsdfsdf' failed: Error during WebSocket handshake: Unexpected response code: 404

Here is the code, should be pretty simple

/* Imports skipped */    

@ServerEndpoint("/websocket/{email}/{type}")
public class SimpleHOTPWebSocket {
    @OnMessage
    public void onMessage(String message, Session session)
            throws IOException, InterruptedException {

        System.out.println("Received: " + message);
    }

    @OnOpen
    public void onOpen(Session session, 
                       EndpointConfig c,
                       @PathParam("email") String email,
                       @PathParam("type") String type) {
        System.out.println("Client " + email + " connected as a " + type);
    }

    @OnClose
    public void onClose(Session session) {
        System.out.println("Connection closed");
    }
}

Here is my pom.xml:

I've tried running it using webapp-runner and jetty as well, so problem must be somewhere in my code or pom.xml.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cf.zelitomas</groupId>
    <artifactId>simplesocket</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>simplesocket</name>
    <pluginRepositories>
        <pluginRepository>
            <id>m.g.o-groups-glassfish</id>
            <url>http://maven.glassfish.org/content/groups/glassfish</url>
        </pluginRepository>
    </pluginRepositories>

    <properties>
        <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>


        <dependency>
            <groupId>org.glassfish.extras</groupId>
            <artifactId>glassfish-embedded-all</artifactId>
            <version>3.1.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>

                    <compilerArguments>
                        <endorseddirs>${endorsed.dir}</endorseddirs>
                    </compilerArguments>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.6</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${endorsed.dir}</outputDirectory>
                            <silent>true</silent>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>javax</groupId>
                                    <artifactId>javaee-endorsed-api</artifactId>
                                    <version>7.0</version>
                                    <type>jar</type>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.glassfish</groupId>
                <artifactId>maven-embedded-glassfish-plugin</artifactId>
                <version>3.0.1</version>
                <configuration>
                    <app>${project.build.directory}/${build.finalName}.war</app>
                    <autoDelete>true</autoDelete>
                    <port>8080</port>
                    <contextRoot>test</contextRoot>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

I'm starting server with mvn clean install embedded-glassfish:run

If you wanted to look at source code or try the war file, please feel free to download it here (war, source) Please, can anyone see what am I doing wrong?

Thanks for your help, I'm struggling with this for more that 10 hours now.

And also sorry for my bad English


Answer:

You mention glassfish-embedded in title, but you also mention you tried with jetty and webapp-runner, so I understand that in "embedded" mode you don't care about which you use.

So I downloaded your sources, tried it, reproduced the issue (same behavior), and... removed almost everything from the POM and added the required bits from scratch, using Jetty because I'm more familiar with it, and it only needs central maven repository (no repo added). Overall it's a lot simpler.

Your code only needs a single dependency: javax.websocket-api. All the rest is configuration for jetty:run (which I configured with context path "/test" to match your expectations, as default is "/").

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cf.zelitomas</groupId>
    <artifactId>simplesocket</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>simplesocket</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <jetty-version>9.4.6.v20170531</jetty-version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>${jetty-version}</version>
                <configuration>
                    <webApp>
                        <contextPath>/test</contextPath>
                    </webApp>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>javax.websocket</groupId>
            <artifactId>javax.websocket-api</artifactId>
            <version>1.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

In short: replace your POM with the above, execute mvn jetty:run, and visit http://localhost:8080/test.

Question:

We are using Websockets from the Grizzly project and had expected that the implementation would allow multiple incoming messages over a connection to be processed at the same time. It appears that this is not the case or there is a configuration step that we have missed. To validate this I have created a modified echo test that delays in the onMessage after echoing the text. When a client sends multiple messages over the same connection the server always blocks until onMessage completes before processing a subsequent message. Is this the expected functionality?

The simplified server code is as follows:

package com.grorange.samples.echo;
import java.util.concurrent.atomic.AtomicBoolean;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.NetworkListener;
import org.glassfish.grizzly.websockets.DataFrame;
import org.glassfish.grizzly.websockets.WebSocket;
import org.glassfish.grizzly.websockets.WebSocketAddOn;
import org.glassfish.grizzly.websockets.WebSocketApplication;
import org.glassfish.grizzly.websockets.WebSocketEngine;

public class Echo extends WebSocketApplication {
    private final AtomicBoolean inMessage = new AtomicBoolean(false);

    @Override
    public void onClose(WebSocket socket, DataFrame frame) {
        super.onClose(socket, frame);
        System.out.println("Disconnected!");
    }

    @Override
    public void onConnect(WebSocket socket) {
        System.out.println("Connected!");
    }

    @Override
    public void onMessage(WebSocket socket, String text) {
        System.out.println("Server: " + text);
        socket.send(text);
        if (this.inMessage.compareAndSet(false, true)) {
            try {
                Thread.sleep(10000);
            } catch (Exception e) {}

            this.inMessage.set(false);
        }
    }

    @Override
    public void onMessage(WebSocket socket, byte[] bytes) {
        socket.send(bytes);
        if (this.inMessage.compareAndSet(false, true)) {
            try {
                Thread.sleep(Long.MAX_VALUE);
            } catch (Exception e) {}

            this.inMessage.set(false);
        }

    }

    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.createSimpleServer("http://0.0.0.0", 8083);
        WebSocketAddOn addOn = new WebSocketAddOn();
        addOn.setTimeoutInSeconds(60);
        for (NetworkListener listener : server.getListeners()) {
            listener.registerAddOn(addOn);
        }

        WebSocketEngine.getEngine().register("", "/Echo", new Echo());
        server.start();
        Thread.sleep(Long.MAX_VALUE);
    }
}

The simplified client code is:


Answer:

Yes, it's expected. The way to go is to pass message processing, inside onMessage, to a different thread.