Search code examples
javaspring-bootwebsocketstomp

Spring Boot WebSocket - Adding body to the CONNECT response


I have the following JS functions, which are connecting to a WebSocket over STOMP, and handling the onConnected events, respectively.

function connect(event) {
    username = document.querySelector('#name').value.trim();

    if(username) {
        usernamePage.classList.add('hidden');
        chatPage.classList.remove('hidden');

        var socket = new SockJS('/ws');
        stompClient = Stomp.over(socket);

        stompClient.connect({}, onConnected, onError);
    }
    event.preventDefault();
}


function onConnected(response) {
    console.log(response);
    // Subscribe to the Public Topic
    stompClient.subscribe('/topic/public', onMessageReceived);

    // Tell your username to the server
    stompClient.send("/app/chat.addUser",
        {},
        JSON.stringify({sender: username, type: 'JOIN'})
    )

    connectingElement.classList.add('hidden');
}

Now the first line of the function onConnected, it logs the following into the console, which apparently looks like something I could add more data into, from the server side.

{
    command: "CONNECTED",
    headers: { 
        "heart-beat": "0,0",
        version: "1.1"
    },
    body: ""
}

I also have an HttpHandshakeInterceptor which implements HandshakeInterceptor as follows.

@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
        Map attributes) throws Exception {
    if (request instanceof ServletServerHttpRequest) {
        ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
        HttpSession session = servletRequest.getServletRequest().getSession();
        attributes.put("sessionId", session.getId());

        ServletServerHttpResponse servletResponse = (ServletServerHttpResponse) response;
        response.getHeaders().set("KEY","VALUE"); // *** I want this either to be in the response header or the body.
    }
    return true;
}

The line that has a comment starting with *** is what I am doing right now with no success. What am I doing wrong? Can I even do this and get some parameters there to the client? If I am doing it wrong, how and where to do it right? (because now I am starting to feel like I am trying to do this in the wrong place)


Solution

  • Okay. So the following is what I have tried to do, with no success.

    public class OutboundMessageInterceptor implements ChannelInterceptor {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(OutboundMessageInterceptor.class);
    
        @Override
        public Message<?> preSend(Message<?> message, MessageChannel channel) {
            final StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message);
            final StompCommand command = headerAccessor.getCommand();
    
            LOGGER.info("Outbound channel preSend (" + command + ")...");
    
            if (command != null) {
    
                switch (command) {
    
                    case CONNECTED:
                        final StompHeaderAccessor accessor = StompHeaderAccessor.create(headerAccessor.getCommand());
                        accessor.setSessionId(headerAccessor.getSessionId());
                        @SuppressWarnings("unchecked")
                        final MultiValueMap<String, String> nativeHeaders = (MultiValueMap<String, String>) headerAccessor.getHeader(StompHeaderAccessor.NATIVE_HEADERS);
                        accessor.addNativeHeaders(nativeHeaders);
    
                        // add custom headers
                        accessor.addNativeHeader("CUSTOM01", "CUSTOM01");
    
                        return MessageBuilder.createMessage(new byte[0], accessor.getMessageHeaders());
                    default:
                        break;
                }
            }
    
            return message;
        }
    }
    

    What this basically does is, intercepting the outbound messages in the server and, if the message is CONNECTED, it will add some custom header (yes I was trying to add body originally, but for testing purposes I stuck to the header for the time being).

    However this method can intercept many STOMP commands, except CONNECTED. I didn't try to intercept others but there probably should be.

    Then I again referred to the STOMP Protocol Spec (thanks to @TimBish for his comment above) and read this statement from there.

    Only the SEND, MESSAGE, and ERROR frames MAY have a body. All other frames MUST NOT have a body.

    So, it seems like we cannot intercept the messages except for SEND, MESSAGE & ERROR. I presume it is because of the adherence to the protocol spec of Spring's STOMP implementation.

    Why I tried doing this was to share some common secret between each client connected and the server, where the secret is determined by the server. Still looking for such implementation.