12

I have been struggling with this for at least two weeks now. I'm pretty new to websockets. I have good experience with rest endpoints.

My use case is simple. Client initiates a websocket connection sending some info to server, and server uses that info and, sends client some info back at some regular interval say every 5 seconds. I followed the tutorial here - https://spring.io/guides/gs/messaging-stomp-websocket/ It works perfectly as explained. As per the above tutorial, client initiates a http request, which gets upgraded to websocket.

In my case front end is an angular 10 application, and the front end developer prefers to use rxjs/websocket and doesn't want to use SockJS client, as he is sure we don't have to support any legacy browsers, and this is where I'm struck. Apparently rxjs/websocket needs url in ws:// protocol.

From the following snippet, I thought my equivalent ws protocol would be ws://localhost:8080/test However, it doesn't seem to work. I'm not sure what is wrong. Any help is greatly appreciated!

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer
{

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config)
    {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/ws/");
    

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry)
    {
        registry.addEndpoint("/test");
    }

}

From the tutorial, I changed app.js, as follows to test this out.

function connect() {
    // var socket = new SockJS('http://localhost:8080/test'); This works perfectly
    // stompClient = Stomp.over(socket);
    ws = new WebSocket('ws://localhost:8080/test');
    stompClient = Stomp.client(ws);

    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/topic/' + $("#site").val(), function (message) {
            showMessageSentFromServer(JSON.stringify(message.body));
        });
    });
}

When I open up the developer tool of chrome and inspect, I see the websocket connection has be established and upgraded or that is what I see. However, in the console, I see an error log as follows. I'm not sure what is wrong.

Screen shot of network:

enter image description here

Console failure log:

stomp.min.js:8 Uncaught DOMException: Failed to construct 'WebSocket': The URL '[object WebSocket]' is invalid.
    at Object.client (http://localhost:8080/webjars/stomp-websocket/stomp.min.js:8:7229)
    at connect (http://localhost:8080/app.js:18:25)
    at HTMLButtonElement.<anonymous> (http://localhost:8080/app.js:54:9)
    at HTMLButtonElement.dispatch (http://localhost:8080/webjars/jquery/jquery.min.js:3:10315)
    at HTMLButtonElement.q.handle (http://localhost:8080/webjars/jquery/jquery.min.js:3:8342)

Now long story short, I managed to disable SockJs on the server side, by removing withSockJS(). So what is my equivalent ws protocol URL?

Also, another challenge I have apart from this is, how do I set up a scheduled process that can send messages to a websocket topic that the client has subscribed, based on the input from the client. I know it is easy to set up a scheduled process using a @Scheduled annotation. But in my case, I wanted some inputs from the client which is required inside the scheduled process.

Also, please share any resources or examples that you have that explains how to implement a websocket stomp client subscription to a topic using rxjs

Rohde Fischer
  • 1,248
  • 2
  • 10
  • 32
Manikdn84
  • 357
  • 4
  • 21
  • 2
    From the [stomp docs](https://stomp-js.github.io/stomp-websocket/codo/extra/docs-src/Usage.md.html): `var url = "ws://localhost:15674/ws"; var client = Stomp.client(url);` - you should pass just url string instead of already created websocket object. – artur grzesiak Jun 15 '21 at 15:50
  • The code posted does not seem to use [Rxjs websocket](https://rxjs.dev/api/webSocket/webSocket) as it should start with lowercase like `ws = new webSocket('ws://localhost:8080/test')` – Constantin Konstantinidis Jun 19 '21 at 14:32
  • @arturgrzesiak you are my saviour! That seem to fix my issue. I just realised how silly was that mistake, but worth a million. Thank you so much! – Manikdn84 Jun 22 '21 at 00:13
  • @arturgrzesiak I'm happy to award my bounty to you, if you can post your comment as an answer within next 3 hours, as I can't award for replies. Thanks again. – Manikdn84 Jun 22 '21 at 00:46

3 Answers3

18

I was able to fix the problem by making two simple changes.

  1. I enabled my backend to support both ws and http protocols by adding two endpoints to the WebSocketConfig as shown below - one with and the other one without sockjs as shown below, which made my backend more flexible in terms of supporting both the protocols to establish websocket connection. I don't know why this wasn't mentioned anywhere in spring docs or else. Perhaps, people thought it is implied!
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer
{

        @Override
        public void configureMessageBroker(MessageBrokerRegistry config)
        {
                config.enableSimpleBroker("/topic");
                config.setApplicationDestinationPrefixes("/ws/");
        }

        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry)
        {
                registry.addEndpoint("/test"); // This will allow you to use ws://localhost:8080/test to establish websocket connection
                registry.addEndpoint("/test").withSockJS(); // This will allow you to use http://localhost:8080/test to establish websocket connection
        }

}
  1. As pointed out by @arturgrzesiak in previous comment, there was an error in the parameter passed to the Stomp.client(url) I was silly and passing wsobject instead of plain url.
stompClient = Stomp.client('ws://localhost:8080/test');

Finally,

If someone wants to use SockJS client to connect, they can connect using

var socket = new SockJS('http://localhost:8080/test');
stompClient = Stomp.over(socket);

If someone wants to use just a plain Websocket object to connect, use the following.

stompClient = Stomp.client('ws://localhost:8080/test');

I posted this solution as this will be useful to someone out there who had a similar painful experience, and they might find it useful.

Manikdn84
  • 357
  • 4
  • 21
  • 1
    Yes, very useful indeed, cheers!. Only thing I'll add is to register the endpoint including allowed origin(s) (SB v2.5.4), e.g. `registry.addEndpoint("/test").setAllowedOrigins("*");` – nkmuturi Nov 01 '21 at 01:18
  • This fixed my issue, thanks. I thought I could create a websocket manually and pass it via .over but .client(url) worked for me! – user227669 Aug 26 '22 at 14:37
4

As stated in the comment:

From the stomp docs: var url = "ws://localhost:15674/ws"; var client = Stomp.client(url); - you should pass just url string instead of already created websocket object.

artur grzesiak
  • 20,230
  • 5
  • 46
  • 56
-1

Im no expert but I though all WebSocket URIs used the scheme ws: or wss: for a secure WebSocket. To get more familiar with WebSockets you should take a look at How Do Websockets Work?.

When I was starting out with Websockets I learned a lot form Androidhive; Building Group Chat App using Sockets

Im not so used to spring Websockets but in the old days when I was using Java EE it was relatively simple. All you had to do was configure a server endpoint with the annotated @ServerEndpoint. Form there you would use the functions with annotation

@OnMessage
public String onMessage(String message, Session session) {
    ...
}

@OnOpen
public void onOpen(Session session) {
    ...
    // Here you can start a session handler
    // which will send message every 5 sec or so.
    s = new SessionHandler(session, id);
    s.start();
}

@OnClose
public void onClose(Session session) {
    ...
}

Here is how you would send messages

public void send(String msg) throws IOException {
    session.getBasicRemote().sendText(msg);
    // session.getBasicRemote().flushBatch(); 
}

And here is the javascript side,

var wsUri = "ws://" + (document.location.hostname === "" ? "localhost" : document.location.hostname) + ":" +
            (document.location.port === "" ? "80" : document.location.port);

var websocket;
function connect() {
    websocket = new WebSocket(wsUri);

    websocket.onopen = function (evt) {
        onOpen(evt);
    };
    websocket.onmessage = function (evt) {
        onMessage(evt);
    };
    websocket.onerror = function (evt) {
        onError(evt);
    };

    websocket.onclose = function (evt) {
        onClose(evt);
    };
}

connect();

I know this is focused on Java EE but maybe you can consider using Java EE, its not so bad anymore. Take a look at this more in depth sample if you are interested How to build applications with the WebSocket API for Java EE and Jakarta EE

Kallinn110
  • 29
  • 4