Search code examples
javaspring-websocketjava-websocket

Connect to Web-Socket-Server at runtime from back-end java class


I have implemented the following Web-Socket-Server using spring. We don't want to use STOMP and JSocks.

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

  public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    registry.addHandler(new WebSocketServerHandler(), "/websocket/user/{userId}");
  }

}




@Service
public class WebSocketServerHandler extends TextWebSocketHandler {

   private List<WebSocketSession> sessions = new ArrayList<WebSocketSession>();

   @Override
   public void handleTextMessage(WebSocketSession session, TextMessage message)
        throws Exception {

        session.sendMessage(message);           
   }

   @Override
   public void afterConnectionEstablished(WebSocketSession session) throws Exception {
     sessions.add(session);
   }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
      sessions = null;
    }

}

Now I can successfully connect to that Web-Socket-Server from front-end client (browser). Now I want to connect to that Web-Socket-Server from some of my java class at Run-Time and then want to send messages to that Web-Socket-Server. Anyone have any idea how I can I do that?

I have added a test Rest Controller as below:

 @RequestMapping(value = "/web-socket/message/{message}")
 public void sendMessage(@PathVariable("message") final String message) throws Exception {
   final String WS_URI = "ws://localhost:8080/web-socket/user/23";

   final CountDownLatch latch = new CountDownLatch(1);
   WebSocketClientHandler handler = new WebSocketClientHandler(latch);
   WebSocketClient client = new StandardWebSocketClient();
   WebSocketSession session = client.doHandshake(handler, WS_URI).get();
   session.sendMessage(new TextMessage(message));
   latch.await(5, TimeUnit.SECONDS);
   session.close();

}

I am successfully able to send message to the websocket server, only for the 1st time by calling this Test RestController using google rest client. If I need to send message again, then I have to restart the tomcat server. Is there anything wrong what I am doing here?


Solution

  • spring-websocket has WebSocket client support. The basic contract is the WebSocketClient. Spring doesn't implement the entire WebSocket support, but offers an abstraction layer over other implementations. The common Spring client abstraction is the StandardWebSocketClient. A simple example look something like

    public static void main(String... args) throws Exception {
        final CountDownLatch latch = new CountDownLatch(1);
        EchoHandler handler = new EchoHandler(latch);
        WebSocketClient client = new StandardWebSocketClient();
        WebSocketSession session = client.doHandshake(handler, ECHO_URL).get();
        session.sendMessage(new TextMessage("Hello World"));
        latch.await(5000, TimeUnit.SECONDS);
        session.close();
    }
    

    Where EchoHandler is a client side WebSocketHandler (same as server side).

    public class EchoHandler extends TextWebSocketHandler {
    
        private final CountDownLatch latch;
    
        public EchoHandler(CountDownLatch latch) {
            this.latch = latch;
        }
    
        @Override
        public void handleTextMessage(WebSocketSession session, TextMessage message) {
            System.out.println("------- received client message ------");
            System.out.println(message.getPayload());
            System.out.println("--------- end client message ---------");
            latch.countDown();
        }
    }
    

    You can also wrap the WebSocketClient in a WebSocketConnectionManager if you are running the client in a String environment. This will give you some lifecycle helpers if you need it. See example in Spring Boot sample.

    As far the the dependencies, like I said, Spring doesn't implement the entire WebSocket client support, so you need an implementation. If you are running the client in a server that supports WebSocket, then you will not need to add anything. The support implementation should already be on the classpath from the server. The main supported WebSocket implementations are Jetty, Tomcat, Undertow (mainly Wildfly or standalone Undertow), Tyrus (i.e. Glassfish, WebLogic).

    If you are running the client in a standalone app, then you will need to add a WebSocket implementation. Unfortunately, from what I tested, none of the implementation provide a complete workable "client-only" jar. They either require or already pull in the complete (server included) implementation. So using just the client, will still require pulling in a bunch of server jars. Here's what I came up with from testing

    Common for all

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-websocket</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>javax.websocket</groupId>
        <artifactId>javax.websocket-api</artifactId>
        <version>${websocket.version}</version>
    </dependency>
    

    Tomcat

    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-websocket</artifactId>
        <version>${tomcat.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-core</artifactId>
        <version>${tomcat.version}</version>
    </dependency>
    

    Undertow

    <dependency>
        <groupId>io.undertow</groupId>
        <artifactId>undertow-websockets-jsr</artifactId>
        <version>${undertow.version}</version>
    </dependency>
    

    Tyrus

    <!-- tyrus-client is pulled in by this. -->
    <dependency>
        <groupId>org.glassfish.tyrus</groupId>
        <artifactId>tyrus-server</artifactId>
        <version>${tyrus.version}</version>
    </dependency>
    

    Jetty

    <dependency>
        <groupId>org.eclipse.jetty.websocket</groupId>
        <artifactId>websocket-client</artifactId>
        <version>${jetty.version}</version>
    </dependency>
    

    With Jetty, it does't use the standard Java WebSocket API, so it is not used in the StandardWebSocketClient. You will need to do instead

    JettyWebSocketClient client = new JettyWebSocketClient();
    client.start();
    

    Everything else above is the same though.

    I just looked at the Spring source to see all the different dependencies they used. You can check it out and play around with the dependencies. Maybe there are different more efficient (lighter) combination of dependencies that will still work. The above is just what I tested with and was able to get to work.