mthizo247 / spring-cloud-netflix-zuul-websocket

Zuul reverse proxy web socket support
Apache License 2.0
139 stars 72 forks source link

Duplicate response messages sent when multiple browser tabs/windows are open #4

Open mlhummon opened 7 years ago

mlhummon commented 7 years ago

I'm using this to send messages to users that may have multiple tabs/windows open. I noticed in this case duplicate messages are sent from the proxy. I can also reproduce this with the demo, https://github.com/mthizo247/zuul-websocket-support-demo.

When using the demo, start the proxy and hello app. Open two tabs or windows going through the proxy, http://localhost:7078/, and click connect on both. Enter your name in the "What's your name?" input box, then click send. Observe in the console that two "MESSAGE" responses are received in both. If you have three tabs or windows open, then three "MESSAGE" responses are sent to each.

If you do the same thing going directly to the hello app, http://localhost:7079/, you don't get the duplicate messages.

mpodlodowski commented 7 years ago

For me it looks like ProxyWebSocketConnectionManager making subscription per raw destination is the issue.

For example: If the user subscribes to /user/topic/messages it should be mapped to something like "/user/xxx/topic-[SESSION_ID], which will make the user receive only messages targeting his session.

/org/springframework/messaging/simp/user/UserDestinationMessageHandler.java might be the hint

@mthizo247 What do you think?

tkec commented 6 years ago

I got the same problem, I tried to send a topic message, the message would be received twice when there was two tabs.

minlingchao1 commented 6 years ago

Do you have solved it?@tkec

tkec commented 6 years ago

No @minlingchao1

minlingchao1 commented 6 years ago

I have solved it

endario commented 6 years ago

@minlingchao1 Could you share your solution, please?

R1310328554 commented 6 years ago

@endario it's hard to solve that , and my solution is let it go , but we need change the project code behind the zuul project, modifying convertAndSend to convertAndSendToUser + loop, or you can say modifying topic to queue + loop , like bellow:

@MessageMapping("/sendToTopic")
public void handleChassst(Principal principal, String msg){
    System.out.println("principal = [" + principal.getName() + "], msg = [" + msg + "]");
    Map<String, Message<?>> tokens = WebSocketConfig.tokens;
    Set<String> tokensSet = tokens.keySet();
    for (Iterator<String> iterator = tokensSet.iterator(); iterator.hasNext(); ) {
        String toUser =  iterator.next();
        simpMessagingTemplate.convertAndSendToUser(toUser, "/queue/notifications",
                toUser + "__"  + content);
    }
}

while tokens contains all the sessions user alive, and then change the method : com.github.mthizo247.cloud.netflix.zuul.web.socket.ProxyWebSocketConnectionManager#handleFrame

to:

@Override
public void handleFrame(StompHeaders headers, Object payload) {
    if (headers.getDestination() != null) {
        String destination = headers.getDestination();
        if (logger.isDebugEnabled()) {
            logger.debug("Received " + payload + ", To " + headers.getDestination());
        }

        Principal principal = userAgentSession.getPrincipal();
        String userDestinationPrefix = messagingTemplate.getUserDestinationPrefix();
        if (destination.startsWith(userDestinationPrefix)) {
            String substring = destination.substring(userDestinationPrefix.length());
            int i = substring.indexOf("/");
            String dest = substring.substring(0, i);
            String[] queues = zuulWebSocketProperties.getQueues();
            String[] topics = zuulWebSocketProperties.getTopics();
            destination = destination.substring(userDestinationPrefix.length());
            destination = destination.startsWith("/") ? destination:"/" + destination;
            if (Arrays.asList(queues).contains(dest)) {
                String payStr = payload.toString();
                int index = payStr.indexOf("__");
                String toUserId = payStr.substring(0, index);
                String msg = payStr.substring(index + "__".length());
                messagingTemplate.convertAndSendToUser(toUserId, destination,
                        msg, copyHeaders(headers.toSingleValueMap()));
            } else if (Arrays.asList(topics).contains(dest)) {
                Set<String> strings = ProxyWebSocketHandler.tokens.keySet();// TODO
                for (Iterator<String> iterator = strings.iterator(); iterator.hasNext(); ) {
                    String next = iterator.next();
                    messagingTemplate.convertAndSendToUser(next, destination,
                            payload, copyHeaders(headers.toSingleValueMap()));
                }
            } else {
                logger.error("Cannot send msg :" + destination);
            }
        } else {
            messagingTemplate.convertAndSend(destination, payload,
                    copyHeaders(headers.toSingleValueMap()));
        }
    }
}
R1310328554 commented 6 years ago

as in my project , we authenticate users by token, so principal is not available, so , i conbined toUserId with the payload, and then convertAndSendToUser ...