In development I have a javascript websocket connecting directly to TomEE and the websocket stays connected with no problems.
In production with TomEE behind an httpd proxy the connection times out after about 30 seconds.
Here is the relevant part of the virtual host config
ProxyPass / ajp://127.0.0.1:8009/ secret=xxxxxxxxxxxx
RewriteEngine on
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://127.0.0.1:8080/$1" [P,L]
I have tried using the reconnecting-websocket npm library but it seems to keep spawning websockets until chrome runs out of memory. The original websockets remain with status 101 rather that changing to finished.
I did read that the firewall can cause it to disconnect but I searched for firewalld and websocket and couldn't find anything
CodePudding user response:
It looks like the answer is to implement "ping pong". This prevents the firewall or proxy from terminating the connection.
If you ping a websocket (client or server) then the specification says it has to respond (pong). But Javascript websocket depends on the browser implementation so it is best to implement a 30 second ping on the server to all clients. e.g.
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.PongMessage;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint(value = "/websockets/admin/autoreply")
public class MyWebSocket {
private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>());
private static final Set<String> alive = Collections.synchronizedSet(new HashSet<String>());
@OnOpen
public void onOpen(Session session) throws IOException {
sessions.add(session);
alive.add(session.getId());
}
@OnMessage
public void onMessage(Session session, String string) throws IOException {
// broadcast(string);
}
@OnMessage
public void onPong(Session session, PongMessage pongMessage) throws IOException {
// System.out.println("pong");
alive.add(session.getId());
}
@OnClose
public void onClose(Session session) throws IOException {
sessions.remove(session);
}
@OnError
public void onError(Session session, Throwable throwable) {
// Do error handling here
}
public void broadcast(String string) {
synchronized (sessions) {
for (Session session : sessions) {
broadcast(session, string);
}
}
}
private void broadcast(Session session, String string) {
try {
session.getBasicRemote().sendText(string);
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void ping() {
synchronized (sessions) {
for (Session session : sessions) {
ping(session);
}
}
}
private void ping(Session session) {
try {
synchronized (alive) {
if (alive.contains(session.getId())) {
String data = "Ping";
ByteBuffer payload = ByteBuffer.wrap(data.getBytes());
session.getBasicRemote().sendPing(payload);
alive.remove(session.getId());
} else {
session.close();
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
and the timer service looks like this
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.ScheduleExpression;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerConfig;
import javax.ejb.TimerService;
import org.apache.tomcat.websocket.server.DefaultServerEndpointConfigurator;
import tld.domain.api.websockets.MyWebSocket;
@Singleton
@Lock(LockType.READ)
@Startup
public class HeartbeatTimer {
@Resource
private TimerService timerService;
@PostConstruct
private void construct() {
final TimerConfig heartbeat = new TimerConfig("heartbeat", false);
timerService.createCalendarTimer(new ScheduleExpression().second("*/30").minute("*").hour("*"), heartbeat);
}
@Timeout
public void timeout(Timer timer) {
if ("heartbeat".equals(timer.getInfo())) {
// System.out.println("Pinging...");
try {
DefaultServerEndpointConfigurator dsec = new DefaultServerEndpointConfigurator();
MyWebSocket ws = dsec.getEndpointInstance(MyWebSocket.class);
ws.ping();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
}