Hi all,

We have a simple servlet which implements Apache CometEvent for long
polling connection on tomcat8. It works well when we used
org.apache.coyote.http11.Http11NioProtocol, however, we have now changed to
using org.apache.coyote.http11.Http11Nio2Protocol and it will not work
properly.

Tomcat: v8.0.23
JDK: v1.8.0_45
OS: Windows server 2008 R2

The connector setting as below:

<Connector port="8443"
protocol="org.apache.coyote.http11.Http11Nio2Protocol"
   maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
   clientAuth="false" sslProtocol="TLS" connectionTimeout="60000"
   keystoreFile="D:\localhost.jks" keystorePass="******" />

The timeout of the event will not work as we have set it to 300 seconds(by
event.setTimeout(300000)), the comet connection will be disconnected after
60 seconds which I believe is the connector connection timeout. And there
will have thrown an exception as below

28-Oct-2016 15:04:33.748 SEVERE [http-nio2-8443-exec-5]
org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process Error
reading request, ignored
java.lang.IllegalStateException: Reading not allowed due to timeout or
cancellation
    at
sun.nio.ch.AsynchronousSocketChannelImpl.read(AsynchronousSocketChannelImpl.java:249)
    at
sun.nio.ch.AsynchronousSocketChannelImpl.read(AsynchronousSocketChannelImpl.java:297)
    at
org.apache.tomcat.util.net.SecureNio2Channel.read(SecureNio2Channel.java:792)
    at
org.apache.tomcat.util.net.Nio2Endpoint.awaitBytes(Nio2Endpoint.java:871)
    at
org.apache.coyote.http11.Http11Nio2Protocol$Http11ConnectionHandler.release(Http11Nio2Protocol.java:180)
    at
org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:722)
    at
org.apache.tomcat.util.net.Nio2Endpoint$SocketProcessor.doRun(Nio2Endpoint.java:1073)
    at
org.apache.tomcat.util.net.Nio2Endpoint$SocketProcessor.run(Nio2Endpoint.java:1032)
    at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)

If the client makes the comet connection again after this, and the other
client tries to send message. The comet will be END immediately and
connection disconnected.


The Connect servlet as below

public class Connect extends HttpServlet implements CometProcessor {

    ...

    public void event(CometEvent event) throws IOException,
ServletException {
        HttpServletRequest request = event.getHttpServletRequest();
        HttpServletResponse response = event.getHttpServletResponse();
        if (event.getEventType() == CometEvent.EventType.BEGIN) {
            String deviceid = request.getParameter("id");
            MessageSender.getInstance().addConnection(deviceid, event);
            request.setAttribute("org.apache.tomcat.comet.timeout", 300 *
1000);
            event.setTimeout(300 * 1000);
        } else if (event.getEventType() == CometEvent.EventType.ERROR) {
            MessageSender.getInstance().removeConnection(event);
            event.close();
        } else if (event.getEventType() == CometEvent.EventType.END) {
            MessageSender.getInstance().removeConnection(event);
            event.close();
        } else if (event.getEventType() == CometEvent.EventType.READ) {
            throw new UnsupportedOperationException("This servlet does not
accept data");
        }
    }
}

And we have another Trigger servlet for sending message to client:

public class Trigger extends HttpServlet {
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        byte[] receieveByteArray = ByteUtil.getHttpServletRequestBody(req);
        sendTrigger(req, resp, receieveByteArray);
    }

    private void sendTrigger(HttpServletRequest req, HttpServletResponse
resp, byte[] trigger) throws IOException, ServletException
    {
        try
        {
            MessageSender.getInstance().sendTrigger(deviceId, trigger);
        } catch (Exception e)
        {
            logger.error("Send trigger has thrown exception: ", e);
        }
    }
}

And the MessageSender class as below

public class MessageSender
{
    private static final Map<String, CometEvent> connections = new
ConcurrentHashMap<String, CometEvent>();

    public void addConnection(String deviceId, CometEvent event) {
        connections.put(deviceId, event);
    }

    public void removeConnection(CometEvent event) {

        while (connections.values().remove(event)) {
    }

    public static MessageSender getInstance() {
        return instance;
    }

    public void sendTrigger(String deviceId, byte[] triggerMessage) throws
IOException, ConnectionNotFoundException {
        CometEvent comet = connections.get(deviceId);
        HttpServletResponse response = comet.getHttpServletResponse();
        response.addHeader("Content-Length",
Integer.toString(triggerMessage.length));
        response.addHeader("Content-Language", "en-US");

        ServletOutputStream servletOutputStream =
response.getOutputStream();
        servletOutputStream.write(triggerMessage);
        servletOutputStream.flush();
        servletOutputStream.close();

        comet.close(); // add for NIO2
        connections.remove(deviceId);
    }
}


Thanks,
Bruce

Reply via email to