- How can I set the comet event timeout on NIO2 protocol?
- How to well handle the socket connection on NIO2 protocol?(e.g., close connection)
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 change to using org.apache.coyote.http11.Http11Nio2Protocol
and it will not work properly.
On NIO, the client can make a comet connection to a Connect servlet by POST and the other client can send message by POST to Trigger servlet. Every 300 seconds we will timeout the comet and the client app will make comet connection again.
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);
}
}
After we have changed the connector setting of tomcat http protocol to NIO2 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 event will not work as we have set it to 300 seconds, 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 make the comet connection again after this, and the other client try to send message to Trigger servlet. The comet will be END immediately and connection disconnected.
Any help is appreciated