Search code examples
javawebsocketjsr356

Server-initiated WebSocket broadcast in JSR-356


What's the best practice to broadcast a server-initiated WebSocket message in JSR-356?

To clarify, I know how a reply or even a broadcast works when using the @OnMessage annotation, but I want to send an event from the server without a receiving a message from a client first. In other words, I suppose I need the reference to the MessageServerEndpoint instance in the code below.

I've seen the following solution, but it uses a static method and is not very elegant.

@ServerEndpoint(value = "/echo")
public class MessageServerEndpoint {
    private static Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>());

    @OnOpen
    public void onOpen(Session session) {
        sessions.add(session);
    }

    @OnClose
    public void onClose(Session session, CloseReason closeReason) {
        sessions.remove(session);
    }

    // Static method - I don't like this at all
    public static void broadcast(String message) {
        for (Session session : sessions) {
            if (session.isOpen()) {
                session.getBasicRemote().sendText(message);
            }
        }
    }
}

public class OtherClass {
    void sendEvent() {
        MessageServerEndpoint.broadcast("test");
        // How do I get a reference to the MessageServerEndpoint instance here instead?
    }
}

Solution

  • I solved the problem by extending ServerEndpointConfig.Configurator and overriding getEndpointInstance() where I can save the endpoint instances:

    public class MyEndpointConfigurator extends ServerEndpointConfig.Configurator
        private Set<MyEndpoint> endpoints = Collections.synchronizedSet(new HashSet<>());
    
        @Override
        public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
            try {
                T endpoint = endpointClass.newInstance();
                MyEndpoint myEndpoint = (MyEndpoint) endpoint;
                myEndpoint.setConfigurator(this);
                endpoints.add(myEndpoint);
                return endpoint;
            } catch (IllegalAccessException e) {
                throw new InstantiationException(e.getMessage());
            }
        }
    
        // Call this from MyEndpoint.onClose()
        public void removeInstance(MyEndpoint endpoint) {
            endpoints.remove(endpoint);
        }
    }
    

    Since I have the reference to MyEndpointConfigurator, I also have the references to all the endpoints.

    It still feels like a hack, but seems to do the trick.